diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index a9b8019ab76c..e356ab3d42e1 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,8 +53,10 @@ 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') }}: - - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + - 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 - group: DotNet-Blob-Feed @@ -72,7 +76,7 @@ variables: /p:DotNetPublishToBlobFeed=$(_DotNetPublishToBlobFeed) /p:DotNetPublishUsingPipelines=$(_PublishUsingPipelines) /p:DotNetArtifactsCategory=$(_DotNetArtifactsCategory) - - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + - ${{ if in(variables['Build.Reason'], 'PullRequest') }}: - name: _BuildArgs value: '' - name: _SignType @@ -82,7 +86,7 @@ variables: # used for post-build phases, internal builds only - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - group: DotNet-AspNet-SDLValidation-Params - + stages: - stage: build displayName: Build @@ -119,10 +123,10 @@ stages: agentOs: Windows steps: - script: "echo ##vso[build.addbuildtag]daily-build" - condition: and(ne(variables['Build.Reason'], 'PullRequest'), notin(variables['DotNetFinalVersionKind'], 'release', 'prerelease')) + condition: and(notin(variables['Build.Reason'], 'PullRequest'), notin(variables['DotNetFinalVersionKind'], 'release', 'prerelease')) displayName: 'Set CI tags' - script: "echo ##vso[build.addbuildtag]release-candidate" - condition: and(ne(variables['Build.Reason'], 'PullRequest'), in(variables['DotNetFinalVersionKind'], 'release', 'prerelease')) + condition: and(notin(variables['Build.Reason'], 'PullRequest'), in(variables['DotNetFinalVersionKind'], 'release', 'prerelease')) displayName: 'Set CI tags' # !!! NOTE !!! Some of these steps have disabled code signing. @@ -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 @@ -676,7 +776,7 @@ stages: displayName: Upload package artifacts # Only capture source build artifacts in PRs for the sake of inspecting # changes that impact source-build. The artifacts from this build pipeline are never actually used. - condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest')) + condition: and(succeeded(), in(variables['Build.Reason'], 'PullRequest')) inputs: pathtoPublish: artifacts/packages/ artifactName: Source_Build_Packages diff --git a/.azure/pipelines/devBuilds.yml b/.azure/pipelines/devBuilds.yml new file mode 100644 index 000000000000..61cb699a4ec4 --- /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 ad17b9963eac..000000000000 --- 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 cba72a653c94..f1596a7b10b1 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 @@ -89,7 +89,7 @@ jobs: # See https://github.com/dotnet/arcade/blob/master/Documentation/ChoosingAMachinePool.md pool: ${{ if eq(parameters.agentOs, 'macOS') }}: - vmImage: macOS-10.13 + vmImage: macOS-10.14 ${{ if eq(parameters.agentOs, 'Linux') }}: vmImage: ubuntu-16.04 ${{ if eq(parameters.agentOs, 'Windows') }}: @@ -97,14 +97,14 @@ jobs: name: NetCorePublic-Pool ${{ if ne(parameters.isTestingJob, true) }}: # Visual Studio Build Tools - queue: BuildPool.Windows.10.Amd64.VS2019.BT.Open + queue: BuildPool.Server.Amd64.VS2019.BT.Open ${{ if eq(parameters.isTestingJob, true) }}: # Visual Studio Enterprise - contains some stuff, like SQL Server and IIS Express, that we use for testing - queue: BuildPool.Windows.10.Amd64.VS2019.Open + queue: BuildPool.Server.Amd64.VS2019.Open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: NetCoreInternal-Pool # Visual Studio Enterprise - contains some stuff, like SQL Server and IIS Express, that we use for testing - queue: BuildPool.Windows.10.Amd64.VS2019 + queue: BuildPool.Server.Amd64.VS2019 variables: - AgentOsName: ${{ parameters.agentOs }} - ASPNETCORE_TEST_LOG_MAXPATH: "200" # Keep test log file name length low enough for artifact zipping @@ -120,9 +120,9 @@ jobs: - ${{ if or(ne(parameters.codeSign, true), ne(variables['System.TeamProject'], 'internal')) }}: - _SignType: '' - ${{ if and(eq(parameters.codeSign, true), eq(variables['System.TeamProject'], 'internal')) }}: - - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + - ${{ if notin(variables['Build.Reason'], 'PullRequest') }}: - _SignType: real - - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + - ${{ if in(variables['Build.Reason'], 'PullRequest') }}: - _SignType: test steps: - checkout: self @@ -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 000000000000..3666d8fd8ca9 --- /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 2f6299934db2..4c68f893d703 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 000000000000..be95a01fc500 --- /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 ff67a9158f13..3225eae5e086 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 103c64248fc0..ce75c6821d3e 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 ed23a80be29e..86f53b9a8a26 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 + -### Additional context -Add any other context about the problem here. -Include the output of `dotnet --info` +### 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 d2dac5f2dad2..d7991eb1e0cb 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 000000000000..2093a9dd863d --- /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 000000000000..b7cd3e4c3132 --- /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 8a2385174b34..99e05dccb927 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 222522b092d3..238c74186d9e 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 47f318869392..c40d2034415a 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,11 +91,8 @@ aspnetcore-runtime aspnetcore-targeting-pack - - + false - true false true @@ -110,6 +114,7 @@ win osx linux + $([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant()) x64 $(TargetOsName)-$(TargetArchitecture) @@ -118,6 +123,7 @@ win-x64; win-x86; win-arm; + win-arm64; osx-x64; linux-musl-x64; linux-musl-arm64; @@ -185,5 +191,6 @@ + diff --git a/Directory.Build.targets b/Directory.Build.targets index bc8bb5dfc815..ccef1aa25c70 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -102,14 +102,16 @@ false true + true - + + $(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).0.0 @@ -132,10 +134,6 @@ $(SharedFxVersion) - - $(NETStandardLibraryRefPackageVersion) - - + + + diff --git a/NuGet.config b/NuGet.config index d71bb19d797c..618ac88e7026 100644 --- a/NuGet.config +++ b/NuGet.config @@ -2,19 +2,14 @@ - - - - - - - - - - - + + + + + + diff --git a/README.md b/README.md index 4b954c6076bf..4c2e975c4268 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 92d052767fc0..5a9569ce1f58 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 81fadeae227f..99d1e377217f 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 cf854f54b4c3..f371d266734b 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 b97417c4fae0..92ef856d4759 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 000000000000..2340d8097baf --- /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 5cc74a5f9d71..3fc873b9392d 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 7e727df9e48d..6c0c734bf8e0 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 e8261acc854a..a7e759cb4251 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 000000000000..e71c96993877 --- /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 1ef20699e689..593682a29ecf 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 043082f4ec63..d84215a6d365 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 000000000000..f257322fab99 --- /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 8d24c40ff552..18e0a87caf1a 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 000000000000..e4d6e72071e0 --- /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 a77163f5726e..a199b6c3de4b 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 42a4992e7de1..6f1b3f190898 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 12f631127e8b..000000000000 --- 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 02f9fb6c5963..000000000000 --- 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 810c09e52c57..ebabc0ac7de5 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 1d5a81f5172a..000000000000 --- 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 dedf038e0b51..dda9b06ce8f1 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 74434eeea5dc..ab7456c178c5 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 000000000000..5fa5fdcaf657 --- /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 e46f5c1120d0..9812152edd7b 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 ddfe60ecfb73..49d28f78f0f2 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 3b61e9205f8c..024a5c8a0aaa 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 82eb3734a2c8..ea6eee484713 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 + dcbab464643d971765e77562d2d0854c6ae112f7 - + https://github.com/dotnet/aspnetcore-tooling - 5ecfad7e0515ee580f7e1b51d1558fc2a1d27ee5 + dcbab464643d971765e77562d2d0854c6ae112f7 - + https://github.com/dotnet/aspnetcore-tooling - 5ecfad7e0515ee580f7e1b51d1558fc2a1d27ee5 + dcbab464643d971765e77562d2d0854c6ae112f7 - + https://github.com/dotnet/aspnetcore-tooling - 5ecfad7e0515ee580f7e1b51d1558fc2a1d27ee5 + dcbab464643d971765e77562d2d0854c6ae112f7 - + https://github.com/dotnet/efcore - 6f4f078e8f92ee8e2ca497fa4b83d4ecebdd643b + fc5681407e88f3a0bcb5e657aa390a08b0169b6f - + https://github.com/dotnet/efcore - 6f4f078e8f92ee8e2ca497fa4b83d4ecebdd643b + fc5681407e88f3a0bcb5e657aa390a08b0169b6f - + https://github.com/dotnet/efcore - 6f4f078e8f92ee8e2ca497fa4b83d4ecebdd643b + fc5681407e88f3a0bcb5e657aa390a08b0169b6f - + https://github.com/dotnet/efcore - 6f4f078e8f92ee8e2ca497fa4b83d4ecebdd643b + fc5681407e88f3a0bcb5e657aa390a08b0169b6f - + https://github.com/dotnet/efcore - 6f4f078e8f92ee8e2ca497fa4b83d4ecebdd643b + fc5681407e88f3a0bcb5e657aa390a08b0169b6f - + https://github.com/dotnet/efcore - 6f4f078e8f92ee8e2ca497fa4b83d4ecebdd643b + fc5681407e88f3a0bcb5e657aa390a08b0169b6f - + https://github.com/dotnet/efcore - 6f4f078e8f92ee8e2ca497fa4b83d4ecebdd643b + fc5681407e88f3a0bcb5e657aa390a08b0169b6f - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 - - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 8a3ffed558ddf943c1efa87d693227722d6af094 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 8a3ffed558ddf943c1efa87d693227722d6af094 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - e946cebe43a510e8c6476bbc8185d1445df33a1a + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/core-setup - 4a9f85e9f89d7f686fef2ae2109d876b1e2eed2a + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/core-setup - 4a9f85e9f89d7f686fef2ae2109d876b1e2eed2a + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/core-setup - 7d57652f33493fa022125b7f63aad0d70c52d810 - - - https://github.com/dotnet/core-setup - 4a9f85e9f89d7f686fef2ae2109d876b1e2eed2a - - - - https://github.com/dotnet/core-setup - 65f04fb6db7a5e198d05dbebd5c4ad21eb018f89 - - - https://github.com/aspnet/Extensions - 4e1be2fb546751c773968d7b40ff7f4b62887153 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - - https://github.com/dotnet/corefx - 0f7f38c4fd323b26da10cce95f857f77f0f09b48 + + https://github.com/dotnet/runtime + 35df31cc5564964617669cb0d6bcce48b90998b4 - + https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 8a574d2004260c3cd8e373937bb9659b81e6bc8d - + https://github.com/dotnet/arcade - 15f00efd583eab4372b2e9ca25bd80ace5b119ad + 16d4350184cf362cd59807b589d1c93803025abc - + https://github.com/dotnet/arcade - 15f00efd583eab4372b2e9ca25bd80ace5b119ad + 16d4350184cf362cd59807b589d1c93803025abc - + https://github.com/dotnet/arcade - 15f00efd583eab4372b2e9ca25bd80ace5b119ad - - - https://github.com/dotnet/extensions - 7d9baaf617741d775578c8018729ac5b75734c69 + 16d4350184cf362cd59807b589d1c93803025abc - + https://github.com/dotnet/roslyn - d8180a5ecafb92adcfbfe8cf9199eb23be1a1ccf + c0fc88e0ddd03abbdac8fc7e0d280b70d7208382 diff --git a/eng/Versions.props b/eng/Versions.props index d28960c8370e..3ebbda029f27 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -6,34 +6,34 @@ --> - 3 - 1 - 3 - 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.20162.3 - 3.4.1-beta4-20127-10 + 3.6.0-2.20166.3 - 3.1.3 - 3.1.3-servicing.20128.1 - 3.1.0 - 3.1.3 - 2.1.0 + 5.0.0-preview.3-runtime.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 - 1.1.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 - 1.8.0 - 4.7.1 - 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.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 + 5.0.0-preview.3.20169.13 - 3.1.0 - - 3.1.0-preview4.19605.1 - - 3.1.3-servicing.20128.2 - 3.1.3-servicing.20128.2 - 3.1.3-servicing.20128.2 - 3.1.3-servicing.20128.2 - 3.1.3-servicing.20128.2 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3-servicing.20128.2 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3-servicing.20128.2 - 3.1.3 - 3.1.3 - 3.1.3-servicing.20128.2 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3-servicing.20128.2 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3-servicing.20128.2 - 3.1.3 - 3.1.3-servicing.20128.2 - 3.1.3-servicing.20128.2 - 3.1.3 - 3.1.0-rtm.19565.4 - 3.1.3 - - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 - - 3.1.3 - 3.1.3 - 3.1.3 - 3.1.3 + 5.0.0-preview.3.20169.13 + + 3.2.0-preview1.20067.1 + + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + 5.0.0-preview.3.20170.1 + + 5.0.0-preview.3.20174.9 + 5.0.0-preview.3.20174.9 + 5.0.0-preview.3.20174.9 + 5.0.0-preview.3.20174.9 + 5.0.0-preview.3.20174.9 + 5.0.0-preview.3.20174.9 + 5.0.0-preview.3.20174.9 + + 5.0.0-preview.3.20170.3 + 5.0.0-preview.3.20170.3 + 5.0.0-preview.3.20170.3 + 5.0.0-preview.3.20170.3 + 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 3ed1473baff2..eeb9004c6d23 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 8f51713dc010..0e2159fd3c25 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 b8d84607b89b..000000000000 --- 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 a1b1333723eb..000000000000 --- 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 5d55e312b012..000000000000 --- 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 a8b5280d9ddd..a5a1e711d79c 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 4ebb1e5a4400..7d6fef27fe49 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 3d0ac80af3ff..000000000000 --- 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 cb2d28cb99e1..000000000000 --- 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 e001ccb481cf..813d440d2a83 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 - +# 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 "" - - 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.)." + 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 6236fc4d38cd..36f9aa0462ee 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" -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" +if [[ "$clean" == true ]]; then + if [ -d "$artifacts_dir" ]; then + rm -rf $artifacts_dir + echo "Artifacts directory deleted." + fi + exit 0 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 a7e1c73501b3..000000000000 --- 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 29415899c1c6..000000000000 --- 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 adceda877add..5e74881eb24c 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 @@ -54,12 +54,11 @@ 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 )" +__Android_Cross_Dir="$(cd "$__CrossDir/../../../.tools/android-rootfs" && 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" +__ToolchainDir="$__Android_Cross_Dir/android-ndk-$__NDK_Version" if [[ -n "$TOOLCHAIN_DIR" ]]; then __ToolchainDir=$TOOLCHAIN_DIR @@ -89,49 +88,33 @@ if [ ! -d $__lldb_Dir ]; then unzip -q $__Android_Cross_Dir/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..." +mkdir -p $__Android_Cross_Dir/tmp/$arch/ -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 - -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/ + if [[ "$path" != "Filename:" ]]; then + echo "Working on: $path" + wget -qO- http://termux.net/$path | dpkg -x - $__Android_Cross_Dir/tmp/$__AndroidArch/ + fi +done cp -R $__Android_Cross_Dir/tmp/$__AndroidArch/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 \ + --subsetCategory libraries \ + --subsetCategory installer diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index d7d5d7d5f449..a23f895ba1c3 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 071d4112419b..1823804da45f 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 b94c2f4e411d..435e7641341b 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 242429bca65e..d981d7bbf38d 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 ec3e739fe836..811f0f717f73 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 d259a274c780..ead6a1d9a24b 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 eccbf9f1b16d..da09da4f1fc4 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 b056e4c1ac2a..0728b1a8b570 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 8cf18bcfebae..db830c00a6f8 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 4dafaaca130f..29fc5db8ae07 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 8b8bafd6a896..db0baac9a445 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 1ff654d2ffcd..5941ea283358 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 b37fd3d5e977..c6401230002f 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 271bddfac5a9..bf272dcf55a5 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 53ddf4e68601..12339a40761d 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 5f1a182fa9f7..18041be87633 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 635ab3fd414b..f397e1c75d41 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 e5826b532370..cf5941e1b645 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 ec41965fc895..e33766992945 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 2f2092166e43..94a04e0fe551 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 af5f48aacebc..5042baebf11d 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) - - $PSBoundParameters.Remove("Category") | Out-Null + [switch]$AsOutput, + [switch]$Force) - $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 1c560a506132..33c3f0d8072a 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 e5ae85f25179..de2d957922a6 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 000000000000..7e6618d64ad9 --- /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 89854d3c1c2a..000000000000 --- 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 78ed0d540f50..dab3534ab538 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 + + $url = 'https://raw.githubusercontent.com/NuGet/NuGetGallery/3e25ad135146676bcab0050a516939d9958bfa5d/src/VerifyMicrosoftPackage/verify.ps1' - New-Item -ItemType "directory" -Path ${ToolDestinationPath} -Force + 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 551ae113f89f..7d49744795f6 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,20 +51,20 @@ function Get-MaestroSubscriptions([string]$SourceRepository, [int]$ChannelId) { return $result } -function Trigger-Subscription([string]$SubscriptionId) { +function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { 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 + $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}/builds/${BuildId}?api-version=$MaestroApiVersion" + Invoke-WebRequest -Method Post -Uri $apiEndpoint -Headers $apiHeaders | Out-Null } -function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { +function Trigger-Subscription([string]$SubscriptionId) { Validate-MaestroVars $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken - $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}/builds/${BuildId}?api-version=$MaestroApiVersion" - Invoke-WebRequest -Method Post -Uri $apiEndpoint -Headers $apiHeaders | Out-Null + $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions/$SubscriptionId/trigger?api-version=$MaestroApiVersion" + Invoke-WebRequest -Uri $apiEndpoint -Headers $apiHeaders -Method Post | Out-Null } function Validate-MaestroVars { @@ -72,18 +73,18 @@ function Validate-MaestroVars { 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 d7f64dc63cbc..000000000000 --- 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 bbfdacca1307..cc9d059d04f3 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 096ac321d129..f7cfe986ddd3 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 926d5b455131..55dea518ac58 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 + 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'." + 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!' + } } -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 d0eec5163efe..3872af59b972 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 01799d63ff36..9db582f279ee 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'; -} -else{ - $RepoName = $Repository; -} + . $PSScriptRoot\..\tools.ps1 -if ($GuardianPackageName) { - $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path "tools" "guardian.cmd")) -} else { - $guardianCliLocation = $GuardianCliLocation -} + #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 + $workingDirectory = (Split-Path $SourceDirectory -Parent) + $ValidPath = Test-Path $guardianCliLocation -if ($ValidPath -eq $False) -{ - Write-Host "Invalid Guardian CLI Location." - exit 1 -} + 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" + & $(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 + 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 } - } 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 ($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 ($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 + 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 { - Write-Host "Could not publish to TSA -- not all required values ($$TsaBranchName, $$BuildNumber) were specified." - exit 1 } } +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + exit 1 +} diff --git a/eng/common/sdl/extract-artifact-packages.ps1 b/eng/common/sdl/extract-artifact-packages.ps1 index 6e6825013bf5..3c9bf106789f 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 c737eb0e71c1..285f1ccdb07e 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 79c707d6d8a0..79d3d355c7e5 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 9bc25314ae21..40a084f79698 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 ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." + ExitWithExitCode 1 } - 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 + + $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-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 -} + $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-Host "Guardian run for $ToolsList using $configParam failed with exit code $LASTEXITCODE." - exit $LASTEXITCODE + 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 52e2ff021d70..640f2b04e240 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 ffda80a197b2..ecebd0f03eb7 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 eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - ${{ 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')) + + - 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/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml index 6a2f98c036f6..c08225a9a975 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 ad9375f5e5c5..dde27800c3f2 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 - inputs: - artifactName: 'BlobArtifacts' - continueOnError: true - - - task: DownloadBuildArtifacts@0 - displayName: Download PDB Artifacts + - task: PowerShell@2 + displayName: Enable cross-org publishing 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 - inputs: - buildType: current - artifactName: PackageArtifacts - - - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - buildType: current - artifactName: BlobArtifacts - - - task: DownloadBuildArtifacts@0 - displayName: Download Asset Manifests + displayName: Download Build Assets + continueOnError: true 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 c4bc1897d81f..08853ec45e0c 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 216d043e4e3b..9505cf170f0f 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')) }} 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 3268ccaa5513..000000000000 --- 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 3c69186f03a7..7be5b0bfad4e 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 - - - 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/ - - - ${{ 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' - - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: current - artifactName: PackageArtifacts - - - 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 }} - - - ${{ 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 - - - 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 - - - ${{ 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 }} + - template: setup-maestro-vars.yml + + - 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}} + + - 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: 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: 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 + + # 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: Enable cross-org publishing + inputs: + filePath: eng\common\enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) + + - 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,47 +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' - -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml - parameters: - artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} - symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'Net_Eng_Latest_Publish' - channelName: '.NET Eng - Latest' - channelId: 2 - 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' + 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: 'Net_Eng_Validation_Publish' - channelName: '.NET Eng - Validation' - channelId: 9 - 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' + 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: 'NetCore_3_Tools_Validation_Publish' - channelName: '.NET 3 Tools - Validation' - channelId: 390 + stageName: 'Net_Eng_Latest_Publish' + channelName: '.NET Eng - Latest' + 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' @@ -166,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: 'NetCore_3_Tools_Publish' - channelName: '.NET 3 Tools' - channelId: 344 + stageName: 'Net_Eng_Validation_Publish' + channelName: '.NET Eng - Validation' + 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' @@ -178,71 +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_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 6b479c3b82a8..000000000000 --- 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 56242b068e15..05e611edb68a 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 b90404435dd7..f67a210d62f3 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 000000000000..f91751fe78e1 --- /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 05df886f55f7..30becf01ea55 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 92a053bd16b4..60c1cd897587 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 94965a8fd2a9..664ac1055bd0 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/helix/content/InstallAppRuntime.ps1 b/eng/helix/content/InstallAppRuntime.ps1 new file mode 100644 index 000000000000..9d9aaffb5c80 --- /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 000000000000..a9346062fad6 --- /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 862e61258267..3754eee5f556 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 000000000000..ac621e1a5d45 --- /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 100644 index 000000000000..45cb1554fab3 --- /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 100644 index 000000000000..6c1c2ff5c673 --- /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 index 0442958ac2f6..db5d2fa5a532 100644 --- 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 935b23647d89..6dec0880d552 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%\ + + 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% -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%" + set PATH=!PATH!;%DOTNET_CLI_HOME%\.dotnet\tools +) -set HELIX=%helixQueue% +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 -if (%targetFrameworkIdentifier%==.NETFramework) ( - xunit.console.exe %target% -xml testResults.xml - exit /b %ERRORLEVEL% +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 ) -%DOTNET_ROOT%\dotnet vstest %target% -lt >discovered.txt +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 +) + +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 + ) ) -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 +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 index e864f097decc..7788f800f9c0 100644 --- 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 -# 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 -fi +exit_code=0 -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 +# Filter syntax: https://github.com/Microsoft/vstest-docs/blob/master/docs/filter.md +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 -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 105134743c19..b90569314536 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 bbccdaacad6d..844da67bba4f 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 000000000000..9e1bc513a26c --- /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 4ed696ec3c93..3fd2664d480f 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 ebc50dad0a59..468f74975193 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 - - # 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" - - # Restore the original global.json file -trap "{ - mv "$reporoot/global.bak.json" "$reporoot/global.json" -}" EXIT - - # Build repo tasks +# +# 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" + +# 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 + +# 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 6cc6bd2dd81f..03e6f56e37bd 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 877665a63bad..e327a7b8868a 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 000000000000..04b351334127 --- /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 000000000000..b6194607cb43 --- /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 8ea26294a254..e77bcd88280d 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 30a5903b9a7a..d4c7a99d524f 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 d3a45dfa4575..154ce92ee6f2 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 062a9d3a8f76..3460edde2e7a 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 59568267bec0..84076d9a414c 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 e77922ebcb71..37a17b714034 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 @@ + + Text="Cannot reference "%(_InvalidReferenceToSharedFxOnlyAssembly.Identity)" directly because it is part of the shared framework and this project is not. Use <FrameworkReference Include="Microsoft.AspNetCore.App" /> instead." /> - + - + - + @@ -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 2be0954cc258..7ba2602d0ce6 100644 --- a/eng/tools/RepoTasks/DownloadFile.cs +++ b/eng/tools/RepoTasks/DownloadFile.cs @@ -22,7 +22,7 @@ public class DownloadFile : Microsoft.Build.Utilities.Task /// 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 @@ private async System.Threading.Tasks.Task ExecuteAsync() return null; } } -} +} \ No newline at end of file diff --git a/eng/tools/RepoTasks/RepoTasks.tasks b/eng/tools/RepoTasks/RepoTasks.tasks index 4916a97ed395..631944feea73 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 b31b8106207d..97cf8d4d4bfe 100644 --- a/global.json +++ b/global.json @@ -1,9 +1,9 @@ { "sdk": { - "version": "3.1.102" + "version": "5.0.100-preview.2.20120.3" }, "tools": { - "dotnet": "3.1.102", + "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.20162.3", + "Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.20162.3" } } diff --git a/src/Analyzers/Analyzers/src/StartupFacts.cs b/src/Analyzers/Analyzers/src/StartupFacts.cs index 221e3ff4c4d3..f5f833429a69 100644 --- a/src/Analyzers/Analyzers/src/StartupFacts.cs +++ b/src/Analyzers/Analyzers/src/StartupFacts.cs @@ -138,6 +138,8 @@ public static bool IsSignalRConfigureMethodGesture(IMethodSymbol symbol) 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 dbfa9a3dcb28..79f8fea63315 100644 --- a/src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs +++ b/src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs @@ -41,7 +41,7 @@ public void AnalyzeSymbol(SymbolAnalysisContext context) if (useRoutingItem != null && useAuthorizationItem == null) { // This looks like - // + // // app.UseAuthorization(); // ... // app.UseRouting(); @@ -60,7 +60,7 @@ public void AnalyzeSymbol(SymbolAnalysisContext context) if (useAuthorizationItem != null) { // This configuration looks like - // + // // app.UseRouting(); // app.UseEndpoints(...); // ... @@ -86,7 +86,7 @@ public void AnalyzeSymbol(SymbolAnalysisContext context) // 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 269c50a394b0..eb748f2ab369 100644 --- a/src/Analyzers/Analyzers/test/AnalyzerTestBase.cs +++ b/src/Analyzers/Analyzers/test/AnalyzerTestBase.cs @@ -56,7 +56,7 @@ private static string GetProjectDirectory() } // 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 0669c7646462..9fcc0ff16750 100644 --- a/src/Analyzers/Analyzers/test/CompilationFeatureDetectorTest.cs +++ b/src/Analyzers/Analyzers/test/CompilationFeatureDetectorTest.cs @@ -29,7 +29,6 @@ public async Task DetectFeaturesAsync_FindsNoFeatures() } [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 87d62d3fdad9..e0847bc0331b 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 fc635ef7f2b4..8345ddc58683 100644 --- a/src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs +++ b/src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs @@ -247,7 +247,7 @@ public async Task StartupAnalyzer_UseAuthorizationConfiguredCorrectly_ReportsNoD [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 @@ public async Task StartupAnalyzer_UseAuthorizationConfiguredBeforeUseRouting_Rep [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 aa65f832580a..000000000000 --- 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 1ec3645965f4..000000000000 --- 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 9a92559a6139..000000000000 --- 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 eb59611a423b..59aa0da5a0bc 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/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp.cs b/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp.cs index 5eb2c8be65a1..4ca6748ccead 100644 --- a/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp.cs +++ b/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp.cs @@ -9,16 +9,16 @@ public partial class AntiforgeryOptions 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 d66b6245db7b..485fc8c9e8b2 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 0e5039295acb..9313175b36e8 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 @@ public override int GetHashCode() 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 030f79c07efc..f88d18bf8a22 100644 --- a/src/Antiforgery/src/Internal/DefaultAntiforgery.cs +++ b/src/Antiforgery/src/Internal/DefaultAntiforgery.cs @@ -102,10 +102,10 @@ public async Task IsRequestValidAsync(HttpContext httpContext) 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 @@ private IAntiforgeryFeature GetTokensInternal(HttpContext httpContext) /// 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 e32fbb85ab09..3df264d48d50 100644 --- a/src/Antiforgery/test/DefaultAntiforgeryTokenGeneratorTest.cs +++ b/src/Antiforgery/test/DefaultAntiforgeryTokenGeneratorTest.cs @@ -149,10 +149,7 @@ public void GenerateRequestToken_ClaimsBasedIdentity() 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 1852b910dadb..67d690a83ae2 100644 --- a/src/Antiforgery/test/DefaultClaimUidExtractorTest.cs +++ b/src/Antiforgery/test/DefaultClaimUidExtractorTest.cs @@ -56,7 +56,7 @@ public void ExtractClaimUid_ClaimsIdentity() 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 4b4a43b6c1ae..d7654d288dd7 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 cc158167418e..487effa8c19c 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 b1f462275857..30e569ead11f 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 41fcf9554ad6..ef41507d5b07 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 6720f825e670..a17773b58dcf 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 cc158167418e..ac4c026e2f20 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 b1f462275857..60da74a37260 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 41fcf9554ad6..b29fbad79cd1 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 2711ae53037d..77ec20937b75 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 d2cb51194b0a..ba0beb09c895 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 8992d007b831..5d4b07647f16 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 972ea62ff830..a9f4eae94d18 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 8f6272c32639..b1b5724cb393 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 @@ public ComponentInternalUsageDiagnosticAnalyzer() 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 8eb86b3212fa..05b98d3b4afb 100644 --- a/src/Components/Analyzers/src/DiagnosticDescriptors.cs +++ b/src/Components/Analyzers/src/DiagnosticDescriptors.cs @@ -10,7 +10,7 @@ internal static class DiagnosticDescriptors // 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 92b07a7ab215..af77a42eccec 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 @@ public InternalUsageAnalyzer(Func isInInternalNamespace, Func 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 AnalyzeNode(SyntaxNodeAnalysisContext context) + private void AnalyzeSymbol(SymbolAnalysisContext context) { - switch (context.Node) + // 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 MemberAccessExpressionSyntax memberAccessSyntax: + 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; - - if (HasInternalAttribute(symbol)) - { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, memberAccessSyntax.Name.GetLocation(), $"{containingType}.{symbol.Name}")); - return; - } - - if (IsInInternalNamespace(containingType) || HasInternalAttribute(containingType)) - { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, memberAccessSyntax.Name.GetLocation(), containingType)); - return; - } - } - return; + VisitDeclarationSymbol(context, @interface, type); } + break; - case ObjectCreationExpressionSyntax creationSyntax: - { - if (context.SemanticModel.GetSymbolInfo(context.Node, context.CancellationToken).Symbol is ISymbol symbol && - symbol.ContainingAssembly != context.Compilation.Assembly) - { - 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; - } - } + case IFieldSymbol field: + VisitDeclarationSymbol(context, field.Type, field); + break; - 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 ClassDeclarationSyntax declarationSyntax: + // Ignore parameters on property-setters. Those will be reported through + // the property analysis. + if (method.MethodKind != MethodKind.PropertySet) { - if (context.SemanticModel.GetDeclaredSymbol(declarationSyntax)?.BaseType is ISymbol symbol && - symbol.ContainingAssembly != context.Compilation.Assembly && - (IsInInternalNamespace(symbol) || HasInternalAttribute(symbol)) && - declarationSyntax.BaseList?.Types.Count > 0) + foreach (var parameter in method.Parameters) { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, declarationSyntax.BaseList.Types[0].GetLocation(), symbol)); + VisitDeclarationSymbol(context, parameter.Type, method); } - - return; } + break; - case ParameterSyntax parameterSyntax: - { - if (context.SemanticModel.GetDeclaredSymbol(parameterSyntax)?.Type is ISymbol symbol && - symbol.ContainingAssembly != context.Compilation.Assembly && - (IsInInternalNamespace(symbol) || HasInternalAttribute(symbol))) - { + case IPropertySymbol property: + VisitDeclarationSymbol(context, property.Type, property); + break; - context.ReportDiagnostic(Diagnostic.Create(_descriptor, parameterSyntax.GetLocation(), symbol)); - } + case IEventSymbol @event: + VisitDeclarationSymbol(context, @event.Type, @event); + break; + } + } - return; - } + // 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; + } + + if (HasInternalAttribute(symbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + _descriptor, + context.Operation.Syntax.GetLocation(), + symbol.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat))); + 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 903de47c78c5..dab047ca116a 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 8c1ae953762e..91bfe2d9cc41 100644 --- a/src/Components/Analyzers/test/AnalyzerTestBase.cs +++ b/src/Components/Analyzers/test/AnalyzerTestBase.cs @@ -56,7 +56,7 @@ private static string GetProjectDirectory() } // 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 000000000000..c7a01a5c630f --- /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 92e2252304af..000000000000 --- 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 10085017d1f8..80b92842f4ba 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 000000000000..9bd27fb960c1 --- /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 000000000000..7ca9dfccf5c8 --- /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 000000000000..0a0bd11b7bee --- /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 415030a01166..000000000000 --- 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 bdd40c2df1ef..000000000000 --- 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 3a91e8a4b85b..53db4b90de52 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 ca0535937a18..1ed5ecbf0e5c 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 @@ public sealed partial class AuthorizeRouteView : Microsoft.AspNetCore.Components { 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 @@ public partial class CascadingAuthenticationState : Microsoft.AspNetCore.Compone { 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 ca0535937a18..1ed5ecbf0e5c 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 @@ public sealed partial class AuthorizeRouteView : Microsoft.AspNetCore.Components { 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 @@ public partial class CascadingAuthenticationState : Microsoft.AspNetCore.Compone { 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 000000000000..40a5d07de431 --- /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 c790a3c8791f..ff63ec3a669c 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyServiceFactoryAdapter.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/IWebAssemblyServiceFactoryAdapter.cs @@ -6,7 +6,7 @@ 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 3a2ccfbaaee2..b90878fdde36 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs @@ -19,6 +19,11 @@ internal class WebAssemblyHost : IWebAssemblyHost 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 d08162a590ec..5182a1660d6a 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs @@ -22,7 +22,7 @@ public static class WebAssemblyHostExtensions 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 fcc879653abb..2cfc0ba093ea 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyServiceFactoryAdapter.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyServiceFactoryAdapter.cs @@ -6,7 +6,7 @@ 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 e98ef0926850..867dec821513 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 c86c1cf30beb..1769dbd91519 100644 --- a/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs +++ b/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs @@ -20,6 +20,11 @@ public bool IsEnabled(LogLevel logLevel) 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 000000000000..60a3d1638b8f --- /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 c93519dfbd7c..b156fde4a2e0 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 d5d37bb83390..000000000000 --- 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 dea217958c98..000000000000 --- 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 3bd530453f8a..000000000000 --- 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 4d4c114158d9..000000000000 --- 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 97331537f28c..000000000000 --- 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 21a28597e1b9..000000000000 --- 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 18637753cc75..000000000000 --- 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 2f5b3297f574..57b1e5b4cd89 100644 --- a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj +++ b/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj @@ -1,41 +1,71 @@ - + - $(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 - + - - - - - - - - + + + - - + + + + + + + + + <_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 26ca818d7f60..a3e099dee67e 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 000000000000..0bcebe22fa12 --- /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 8067cdc131b9..37e2b60e16f0 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 000000000000..1aa853685671 --- /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 000000000000..d5dc22cde02c --- /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 000000000000..1984de0a5798 --- /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 000000000000..8a56b7fc3deb --- /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 000000000000..1181ea337d72 --- /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 690a29fbab8f..d1d242f4d92f 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 dd4fbf1b7a9a..6c69e85a4011 100644 --- a/src/Components/Blazor/Build/src/targets/All.targets +++ b/src/Components/Blazor/Build/src/targets/All.targets @@ -6,40 +6,43 @@ - $(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 03f70748ffb6..f49c1f8f2ff5 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 69976d519b68..3c7d12656192 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 - - $(TargetDir)$(BaseBlazorRuntimeBinOutputPath) + <_BlazorApplicationAssembliesCacheFile>$(BlazorIntermediateOutputPath)unlinked.output - - - + + <_WebAssemblyBCLFolder Include=" + $(DotNetWebAssemblyBCLPath); + $(DotNetWebAssemblyBCLFacadesPath); + $(DotNetWebAssemblyFrameworkPath)" /> - - - - - - - - <_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" /> + <_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" /> + - <_BlazorCommonInput Include="@(IntermediateAssembly)" /> - <_BlazorCommonInput Include="@(_BlazorDependencyInput)" /> - <_BlazorCommonInput Include="$(_BlazorShouldLinkApplicationAssemblies)" /> - <_BlazorCommonInput Include="$(BlazorEnableDebugging)" /> - <_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''" Include="false" /> - <_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''" Include="true" /> - + + <_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" /> - - - - - - - - - - + + <_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" /> + <_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" /> - - - - + <_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" /> + <_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" /> + - + + + + + + <_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'" /> - + - - - <_PrepareLinkerDescriptorAssemblyLine Include="@(IntermediateAssembly->'%(FileName)')" /> - <_GeneratedLinkerDescriptorLine Include="<linker>" /> - <_GeneratedLinkerDescriptorLine Include="@(_PrepareLinkerDescriptorAssemblyLine->'<assembly fullname="%(Identity)" />')" /> - <_GeneratedLinkerDescriptorLine Include="</linker>" /> - + Condition="'@(BlazorLinkerDescriptor)' == ''"> - + - - - - - - - - - + - <_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" /> - - - - - - - - - - - - - - - <_CollectResolvedAssembliesDependsOn> - _ResolveBlazorApplicationAssemblies; - _ReadResolvedBlazorApplicationAssemblies; - _IntermediateCopyBlazorApplicationAssemblies; - _TouchBlazorApplicationAssemblies - - - - + - - - - $(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)" + + <_DotNetHostDirectory>$(NetCoreRoot) + <_DotNetHostFileName>dotnet + <_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe - - - - - - - - - - - - - - <_IntermediateResolvedRuntimeDependencies Include="@(_BlazorResolvedRuntimeDependencies->'$(BlazorIntermediateResolvedApplicationAssembliesOutputPath)%(FileName)%(Extension)')" /> - + - - + <_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" /> + <_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" Condition="'$(BlazorEnableDebugging)' == 'true'" /> + + - - + Name="_ResolveBlazorOutputsWhenNotLinked" + DependsOnTargets="_ResolveBlazorRuntimeDependencies" + Condition="'$(BlazorLinkOnBuild)' != 'true'"> + + + - - - - - - + + - - - - - - - - + + - + - + - + + - <_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 3275831dca42..6f36c720896f 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 e431ad1272cd..7cb7e0ad231d 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 000000000000..d547f500b13f --- /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 1fafb9e81dbd..2292df38d8ac 100644 --- a/src/Components/Blazor/Build/test/BindRazorIntegrationTest.cs +++ b/src/Components/Blazor/Build/test/BindRazorIntegrationTest.cs @@ -448,7 +448,7 @@ public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_WithCSharp 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 000000000000..4470546cf071 --- /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 3632a082b780..1e2d89b573bb 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 000000000000..8d0aa4b6dab1 --- /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 000000000000..73d864502989 --- /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 000000000000..54c089e874f3 --- /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 000000000000..58b5499e8bc6 --- /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 000000000000..b7e16ca07291 --- /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 000000000000..9a83df922b01 --- /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 000000000000..e50b750ae491 --- /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 000000000000..23badb4296f8 --- /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 000000000000..81f9577e25a7 --- /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 d15cf4f58499..f4a45c7e2f80 100644 --- a/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs +++ b/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs @@ -96,7 +96,7 @@ public class MyComponent : ComponentBase } [Fact] - public void Render_ChildComponent_TriesToSetNonParamter() + public void Render_ChildComponent_TriesToSetNonParameter() { // Arrange AdditionalSyntaxTrees.Add(Parse(@" @@ -408,7 +408,7 @@ public class MyComponent : ComponentBase 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 @@ public void OnComponentHover(MouseEventArgs e) 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 @@ public void Render_Component_HtmlBlockEncoded() } // 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 eee74d8755e6..56eccd10909b 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 5838d419d7aa..64aad9bf4565 100644 --- a/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs +++ b/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs @@ -9,12 +9,12 @@ 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 @@ uncalled implementation code from mscorlib.dll anyway. // 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 000000000000..f60dc0021d63 --- /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 000000000000..e2efcc0c7428 --- /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 000000000000..1b4127e1f49b --- /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 000000000000..944699cdb327 --- /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 000000000000..7081842748da --- /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 000000000000..94e866815d23 --- /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 000000000000..5f282702bb03 --- /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 000000000000..eba23da9b5ae --- /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 000000000000..16dac3192520 --- /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 000000000000..3e46e6331662 --- /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 000000000000..129b440e8600 --- /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 000000000000..1b13eb3d5357 --- /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 000000000000..85994d6e89fe --- /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 4749a781cabe..366a1bce5efd 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 a90d83b4cc93..c48cf8a1a9db 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 178608d3e5ef..e1a17eb9ca9c 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 9c5974d9d6f7..9d6deb6173bb 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 000000000000..60c0cdc42913 --- /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 000000000000..ea714b2d92dc --- /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 000000000000..654263a12331 --- /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 ab6c3b6ee62d..4bdbc1bb1b38 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 533cd9939985..cbe0fe363a67 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.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 @@ public static void UseBlazorDebugging(this IApplicationBuilder app) { app.UseWebSockets(); + app.UseVisualStudioDebuggerConnectionRequestHandlers(); + app.Use((context, next) => { var requestPath = context.Request.Path; @@ -52,6 +63,85 @@ public static void UseBlazorDebugging(this IApplicationBuilder app) }); } + 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 @@ private static async Task DebugHome(HttpContext context) // 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 @@ There is more than one browser tab at {targetTabUrl}. 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 @@ private static string GetLaunchChromeInstructions(string appRootUrl) 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 @@ private static string GetLaunchEdgeInstructions(string appRootUrl) } } - 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 8c440da1ce24..7aa4dcf710ae 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 @@ protected override async Task AcceptEvent (string method, JObject args, Ca 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 @@ async Task GetDetails(int msg_id, int object_id, CancellationToken token, string 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 @@ async Task GetScopeProperties (int msg_id, int scope_id, CancellationToken token 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 @@ async Task RuntimeReady (CancellationToken token) 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 @@ async Task SetBreakPoint (int msg_id, BreakPointRequest req, CancellationToken t 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 ed5e01501483..000000000000 --- 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 7c6f423adde2..000000000000 --- 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 55364eee45fc..000000000000 --- 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 fd1975023109..000000000000 --- 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 2a77f0c7cc91..000000000000 --- 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 89023db6b921..53cc678edbb0 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 ef12ac3c4e83..e27de695c1b3 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 afc09e4f772e..1350803e6b1a 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 4600066f3872..000000000000 --- 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 5297f5bca69a..e2158c8c06ae 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 c4d1ab60e25c..8a42e8e5d1c4 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 48d4530d3e63..328acacdff97 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 8023ded4d9d3..1d56128e3546 100644 --- a/src/Components/Blazor/testassets/MonoSanityClient/Examples.cs +++ b/src/Components/Blazor/testassets/MonoSanityClient/Examples.cs @@ -31,11 +31,6 @@ public static void TriggerException(string message) 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 b186c391941b..e01c60084327 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 cddd429b6a40..32156c56b854 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 5da6ba26b31b..fde34bd63924 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 ba0b2476ffb3..c88695cf66e3 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 a893d64abd8e..f40a0a40987a 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 fe78ce3bd49f..26646bf79bb8 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 @@ internal partial interface IEventCallback 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 f1aa3dab10f0..50f888c26a64 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 a62693a5d952..17c4d7a4f5cd 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 @@ public static partial class BindConverter 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 @@ public static partial class BindConverter 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 @@ public static partial class BindConverter 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 @@ public static partial class BindConverter 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 @@ protected void OnUnhandledException(System.UnhandledExceptionEventArgs e) { } 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 @@ public static partial class EventCallbackFactoryBinderExtensions 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 @@ public static partial class EventCallbackFactoryBinderExtensions 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 @@ public static partial class EventCallbackFactoryEventArgsExtensions 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 @@ public InjectAttribute() { } 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 @@ public LocationChangeException(string message, System.Exception innerException) 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 @@ protected void NotifyLocationChanged(bool isInterceptedLink) { } 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 @@ protected OwningComponentBase() { } 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 @@ public void Render(Microsoft.AspNetCore.Components.RenderFragment renderFragment 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 @@ public readonly partial struct ArrayRange 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 @@ public partial interface INavigationInterception 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 a62693a5d952..17c4d7a4f5cd 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 @@ public static partial class BindConverter 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 @@ public static partial class BindConverter 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 @@ public static partial class BindConverter 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 @@ public static partial class BindConverter 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 @@ protected void OnUnhandledException(System.UnhandledExceptionEventArgs e) { } 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 @@ public static partial class EventCallbackFactoryBinderExtensions 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 @@ public static partial class EventCallbackFactoryBinderExtensions 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 @@ public static partial class EventCallbackFactoryEventArgsExtensions 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 @@ public InjectAttribute() { } 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 @@ public LocationChangeException(string message, System.Exception innerException) 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 @@ protected void NotifyLocationChanged(bool isInterceptedLink) { } 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 @@ protected OwningComponentBase() { } 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 @@ public void Render(Microsoft.AspNetCore.Components.RenderFragment renderFragment 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 @@ public readonly partial struct ArrayRange 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 @@ public partial interface INavigationInterception 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 f77689bde2bf..0aadb8077eb9 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 @@ private static string FormatNullableLongValueCore(long? value, CultureInfo cultu 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 @@ private static string FormatNullableDateTimeOffsetValueCore(DateTimeOffset? valu 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 @@ private static string FormatNullableEnumValueCore(T? value, CultureInfo cultu 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 @@ private static bool ConvertToNullableLongCore(object obj, CultureInfo culture, o 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 @@ private static class FormatterDelegateCache 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 @@ public static BindParser Get() { 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 ffc17a6049ee..4907b3e27e35 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 @@ public sealed class BindElementAttribute : Attribute /// 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 @@ public BindElementAttribute(string element, string suffix, string valueAttribute /// /// 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 db03b3d4161f..605d24134d28 100644 --- a/src/Components/Components/src/CascadingValue.cs +++ b/src/Components/Components/src/CascadingValue.cs @@ -105,7 +105,7 @@ public Task SetParametersAsync(ParameterView parameters) _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 51c95f386b1f..d4b38db34599 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 @@ protected virtual bool ShouldRender() /// /// /// 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 @@ protected virtual void OnAfterRender(bool firstRender) /// 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 @@ private async Task RunInitAndSetParametersAsync() } 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 @@ private async Task CallStateHasChangedOnAsyncCompletion(Task task) } 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 99eac1965a06..0305c1b469ea 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 @@ public static EventCallback CreateBinder( 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 @@ public static EventCallback CreateBinder( 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 6bc25e8d43aa..fefadced4988 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 700ea95fa92c..7017ce828ea7 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 8561bc2f2f14..9ea33e53dc8e 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 d75077026f06..0ad565fb54c0 100644 --- a/src/Components/Components/src/NavigationManager.cs +++ b/src/Components/Components/src/NavigationManager.cs @@ -7,7 +7,7 @@ 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 @@ protected void Initialize(string baseUri, string uri) } /// - /// 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 e73119e03800..1e6249c8d779 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 05cfb41abe24..09470cebda2c 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -142,7 +142,7 @@ protected async Task RenderRootComponentAsync(int componentId, ParameterView ini // 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 @@ private ComponentState GetOptionalComponentState(int componentId) : 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 f8a28273e4d3..3cc4aed9bf12 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 @@ public void AddAttribute(int sequence, string name, EventCallback value) 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 176e4d83e456..d25d50b6de3a 100644 --- a/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs +++ b/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs @@ -147,20 +147,20 @@ public override void Post(SendOrPostCallback d, object state) // 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 @@ private void ExecuteSynchronouslyIfPossible(SendOrPostCallback d, object state) 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 @@ private Task Enqueue(Task antecedant, SendOrPostCallback d, object state, bool f } 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 a9066647414b..522d027d6abe 100644 --- a/src/Components/Components/test/CascadingParameterTest.cs +++ b/src/Components/Components/test/CascadingParameterTest.cs @@ -222,7 +222,7 @@ public void StopsNotifyingDescendantsIfTheyAreRemoved() // 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 9df9feab9ff6..7dd8537f4c51 100644 --- a/src/Components/Components/test/ParameterViewTest.Assignment.cs +++ b/src/Components/Components/test/ParameterViewTest.Assignment.cs @@ -73,7 +73,7 @@ public void IncomingParameterMatchesInheritedDeclaredParameter_SetsValue() [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 @@ public void SettingCaptureUnmatchedValuesParameterExplicitlyAndImplicitly_Revers public void HasDuplicateCaptureUnmatchedValuesParameters_Throws() { // Arrange - var target = new HasDupliateCaptureUnmatchedValuesProperty(); + var target = new HasDuplicateCaptureUnmatchedValuesProperty(); var parameters = new ParameterViewBuilder().Build(); // Act @@ -374,17 +374,17 @@ public void HasDuplicateCaptureUnmatchedValuesParameters_Throws() // 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 @@ class HasCaptureUnmatchedValuesPropertyAndCascadingParameter [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 28f617eba268..9f3ba7180988 100644 --- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs @@ -442,7 +442,7 @@ public void HandlesDeletionOfUnkeyedItemsAroundKey() [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 @@ public void HandlesKeyBeingAdded() [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 d0e2affea238..77e0b06faa71 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -2575,7 +2575,7 @@ public void QueuedRenderIsSkippedIfComponentWasAlreadyDisposedInSameBatch() [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 @@ public void CanTriggerRenderingSynchronouslyFromInsideAfterRenderCallback() } [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 @@ public void DisposingRenderer_CapturesExceptionsFromAllRegisteredComponents() // 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 31364dca008f..b4ad71cba2ad 100644 --- a/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs +++ b/src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs @@ -301,7 +301,7 @@ public void CanAddMultipleAttributes_InterspersedWithOtherAttributes() [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 568d2501bbb8..c92a585bd623 100644 --- a/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs +++ b/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs @@ -40,7 +40,7 @@ public void Post_RunsAsynchronously_WhenNotBusy() } [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 92bea90d7998..e596f2795678 100644 --- a/src/Components/Components/test/Routing/RouteTableFactoryTests.cs +++ b/src/Components/Components/test/Routing/RouteTableFactoryTests.cs @@ -369,7 +369,7 @@ public void ProducesAStableOrderForNonAmbiguousRoutes() [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 09f6a0859f95..7e09eeea25ce 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 02d423b43e89..b614476c4c1d 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 b992960cc31d..b6b1f773d930 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 2ef391ee71ab..0709f6d84b9f 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 4674ff0fb4e2..c1961a8a25f0 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 @@ protected override void OnInitialized() { } 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 @@ public static partial class EditContextDataAnnotationsExtensions 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 4674ff0fb4e2..c1961a8a25f0 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 @@ protected override void OnInitialized() { } 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 @@ public static partial class EditContextDataAnnotationsExtensions 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 3bb37c38ce1b..34d128ecc53b 100644 --- a/src/Components/Ignitor/src/ElementHive.cs +++ b/src/Components/Ignitor/src/ElementHive.cs @@ -42,7 +42,7 @@ public bool TryFindElementById(string id, [NotNullWhen(true)] out ElementNode? e 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 @@ public bool TryFindElementById(string id, [NotNullWhen(true)] out ElementNode? e 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 6eacb16cdaaa..6f7ed330ff32 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 b9cfbda974ba..6265d868b9f3 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 d64c31e7da67..03c8c551e38b 100644 --- a/src/Components/Server/src/CircuitDisconnectMiddleware.cs +++ b/src/Components/Server/src/CircuitDisconnectMiddleware.cs @@ -9,7 +9,7 @@ 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 @@ private class Log 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 68ca25c85aa8..9f862b069d4c 100644 --- a/src/Components/Server/src/CircuitOptions.cs +++ b/src/Components/Server/src/CircuitOptions.cs @@ -19,7 +19,7 @@ public sealed class CircuitOptions /// 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 @@ public sealed class CircuitOptions /// 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 47a04da3a9a0..0c43ce269918 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -356,7 +356,7 @@ await Renderer.Dispatcher.InvokeAsync(() => // 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 @@ public async Task EndInvokeJSFromDotNet(long asyncCall, bool succeded, string ar { 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 @@ private async Task TryNotifyClientErrorAsync(IClientProxy client, string error, 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 @@ private static class EventIds 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 @@ private static class EventIds 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 @@ static Log() 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 @@ static Log() _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 @@ static Log() _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 @@ static Log() "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 544a1b791c5e..7250ee811653 100644 --- a/src/Components/Server/src/Circuits/CircuitIdFactory.cs +++ b/src/Components/Server/src/Circuits/CircuitIdFactory.cs @@ -20,7 +20,6 @@ internal class CircuitIdFactory 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 @@ public CircuitIdFactory(IDataProtectionProvider provider) 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 ab261a108baf..f8e4971a6497 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 @@ public virtual Task DisconnectAsync(CircuitHost circuitHost, string connectionId 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 @@ public virtual async Task ConnectAsync(CircuitId circuitId, IClient { // 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 @@ static Log() _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 0ef78bcf0f48..1d9205e1bae7 100644 --- a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs +++ b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs @@ -74,7 +74,7 @@ protected override void BeginInvokeJS(long asyncHandle, string identifier, strin { 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 509944e17a1a..e7ab9c1a4c0e 100644 --- a/src/Components/Server/src/Circuits/RemoteRenderer.cs +++ b/src/Components/Server/src/Circuits/RemoteRenderer.cs @@ -25,7 +25,7 @@ internal class RemoteRenderer : Microsoft.AspNetCore.Components.RenderTree.Rende private bool _disposing = false; /// - /// Notifies when a rendering exception occured. + /// Notifies when a rendering exception occurred. /// public event EventHandler UnhandledException; @@ -90,14 +90,14 @@ protected override void ProcessPendingRender() // 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 @@ private async Task WriteBatchBytesAsync(UnacknowledgedRenderBatch pending) { // 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 @@ public Task OnRenderCompletedAsync(long incomingBatchId, string errorMessageOrNu // 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 @@ public Task OnRenderCompletedAsync(long incomingBatchId, string errorMessageOrNu 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 cb347a30be2f..779b4b005341 100644 --- a/src/Components/Server/src/ComponentHub.cs +++ b/src/Components/Server/src/ComponentHub.cs @@ -302,7 +302,7 @@ private static class Log 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 000000000000..09953b9b6c9d --- /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 b47c1c2fe36a..98fa33ca3e84 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 35bc4544168b..588eadfdd8e5 100644 --- a/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs +++ b/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs @@ -25,7 +25,7 @@ public void CreateCircuitId_Generates_NewRandomId() } [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 e7cda62bb2f5..6befe0c65d0d 100644 --- a/src/Components/Server/test/Circuits/RemoteRendererTest.cs +++ b/src/Components/Server/test/Circuits/RemoteRendererTest.cs @@ -179,7 +179,7 @@ public async Task ProcessBufferedRenderBatches_WritesRenders() // 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 @@ await renderer.RenderComponentAsync( 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 dc7d28d50269..ef4eae3630b6 100644 --- a/src/Components/Server/test/Circuits/RevalidatingServerAuthenticationStateProvider.cs +++ b/src/Components/Server/test/Circuits/RevalidatingServerAuthenticationStateProvider.cs @@ -9,6 +9,7 @@ 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 @@ public async Task SuppliesCancellationTokenThatSignalsWhenRevalidationLoopIsBein } [Fact] - public async Task IfValidateAuthenticationStateAsyncReturnsUnrelatedCancelledTask_TreatAsFailure() + [QuarantinedTest] + public async Task IfValidateAuthenticationStateAsyncReturnsUnrelatedCanceledTask_TreatAsFailure() { // Arrange var validationTcs = new TaskCompletionSource(); @@ -200,11 +202,11 @@ public async Task IfValidateAuthenticationStateAsyncReturnsUnrelatedCancelledTas 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 e0a6d8ff4452..03a651d7be87 100644 --- a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs +++ b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs @@ -66,9 +66,9 @@ private IApplicationBuilder CreateAppBuilder() 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 c6e294a63ca6..2463622ac4a3 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 465a688bf278..80a7738107a6 100644 --- a/src/Components/Shared/src/ElementReferenceJsonConverter.cs +++ b/src/Components/Shared/src/ElementReferenceJsonConverter.cs @@ -30,7 +30,7 @@ public override ElementReference Read(ref Utf8JsonReader reader, Type typeToConv } else { - throw new JsonException($"Unexcepted JSON Token {reader.TokenType}."); + throw new JsonException($"Unexpected JSON Token {reader.TokenType}."); } } @@ -49,4 +49,4 @@ public override void Write(Utf8JsonWriter writer, ElementReference value, JsonSe 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 89ca1453249a..915e6f019090 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 30d3ae39491b..3c88d5b5a329 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 5279765b23d8..f0b6c6e59827 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 ca36ee98080f..e750da915cc4 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 4ea227247c0d..a7934b51124d 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 74bd496b0285..fcc7a8f5a94c 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 b8f755d8acb4..58d9eb7e37dd 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 311df1b43ec8..f4548eed47c9 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 997b3d7bca6e..321a708f579d 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 783af016f711..7d2f5c23bf03 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 bb2f52113b50..8d5daf454a3f 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 67a01446b909..659674150920 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 577c815f5870..5b4aac4295c2 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 5f217d365930..2fca100f8389 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 81e283fc0db3..5936d31a0171 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 6fcffaae8afc..4bbabdcfc882 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 18b1e18b023a..bb7191613346 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 4928dc03fd43..15e46654546e 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 @@ public partial class EditForm : Microsoft.AspNetCore.Components.ComponentBase { 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 @@ public abstract partial class InputBase : Microsoft.AspNetCore.Component { 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 @@ public partial class InputDate : Microsoft.AspNetCore.Components.Forms.I { 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 @@ public partial class InputNumber : 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 @@ public partial class InputSelect : 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 @@ public partial class ValidationMessage : Microsoft.AspNetCore.Components { 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 @@ public partial class ValidationSummary : Microsoft.AspNetCore.Components.Compone { 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 @@ public partial class NavLink : Microsoft.AspNetCore.Components.ComponentBase, Sy { 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 @@ public static partial class BindAttributes 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 @@ public static partial class EventHandlers 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 @@ public static void AddEventStopPropagationAttribute(this Microsoft.AspNetCore.Co 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 4928dc03fd43..15e46654546e 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 @@ public partial class EditForm : Microsoft.AspNetCore.Components.ComponentBase { 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 @@ public abstract partial class InputBase : Microsoft.AspNetCore.Component { 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 @@ public partial class InputDate : Microsoft.AspNetCore.Components.Forms.I { 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 @@ public partial class InputNumber : 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 @@ public partial class InputSelect : 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 @@ public partial class ValidationMessage : Microsoft.AspNetCore.Components { 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 @@ public partial class ValidationSummary : Microsoft.AspNetCore.Components.Compone { 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 @@ public partial class NavLink : Microsoft.AspNetCore.Components.ComponentBase, Sy { 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 @@ public static partial class BindAttributes 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 @@ public static partial class EventHandlers 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 @@ public static void AddEventStopPropagationAttribute(this Microsoft.AspNetCore.Co 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 0cba1a8e84e7..f6169c0ca562 100644 --- a/src/Components/Web/src/BindInputElementAttribute.cs +++ b/src/Components/Web/src/BindInputElementAttribute.cs @@ -7,7 +7,7 @@ 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 @@ public sealed class BindInputElementAttribute : Attribute /// /// 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 @@ public BindInputElementAttribute(string type, string suffix, string valueAttribu } /// - /// 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 5b2a08a3b804..efdb53c90546 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 @@ static InputNumber() 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 @@ protected override bool TryParseValueFromString(string value, out TValue result, } /// - /// 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 @@ protected override string FormatValueAsString(TValue value) 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 b8cdbeab1f4e..e1a3ab0eae01 100644 --- a/src/Components/Web/src/Forms/InputSelect.cs +++ b/src/Components/Web/src/Forms/InputSelect.cs @@ -17,6 +17,8 @@ public class InputSelect : InputBase /// [Parameter] public RenderFragment ChildContent { get; set; } + private readonly Type _nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(TValue)); + /// protected override void BuildRenderTree(RenderTreeBuilder builder) { @@ -38,7 +40,7 @@ protected override bool TryParseValueFromString(string value, out TValue result, 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 d033fdba20e8..d15efd2d4cf1 100644 --- a/src/Components/Web/src/Forms/ValidationMessage.cs +++ b/src/Components/Web/src/Forms/ValidationMessage.cs @@ -80,11 +80,6 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) } } - 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 270f7871766f..4e168f35baca 100644 --- a/src/Components/Web/src/Forms/ValidationSummary.cs +++ b/src/Components/Web/src/Forms/ValidationSummary.cs @@ -92,11 +92,6 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) } } - 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 ce1bf7bfa2e1..285361d53819 100644 --- a/src/Components/Web/test/Forms/InputBaseTest.cs +++ b/src/Components/Web/test/Forms/InputBaseTest.cs @@ -212,7 +212,7 @@ public async Task SuppliesFieldClassCorrespondingToFieldState() }; 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 000000000000..7c49fe03be56 --- /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 000000000000..8c119d5413b5 --- /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 2c17218233b0..07c80c9790f4 100644 --- a/src/Components/benchmarkapps/BlazingPizza.Server/ToppingsService.cs +++ b/src/Components/benchmarkapps/BlazingPizza.Server/ToppingsService.cs @@ -90,7 +90,7 @@ public IList GetToppings() }, 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 000000000000..62016cf630e6 --- /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 000000000000..ab98fef891b9 --- /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 000000000000..7a32ce146d58 --- /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 000000000000..3173341e4b86 --- /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 000000000000..7a4af028dfff --- /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 000000000000..cfaa9cef0fc9 --- /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 000000000000..1c30e69e20b0 --- /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 000000000000..026ce4b98a80 --- /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 000000000000..bed61b254f6f --- /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 000000000000..9522ecc50248 --- /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 bdf98fd38879..81cd361dce7c 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 f498eb0222b9..403bc37c9c35 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 7422cd806cff..c79b0efb8c38 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 9f796fdbcf9c..3d28c77a21e4 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 dc263c9383a5..fef56339a95f 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 000000000000..c1690cfac87d --- /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 82144199822c..241721ceeb13 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'); - - 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); - }; +function onBenchmarkEvent(fn) { + reportBenchmarkEvent = fn; - benchmark.on('changed', state => this.updateDisplay(state)); - this.updateDisplay(this.benchmark.state); - } + groups.forEach(group$$1 => { + group$$1.on('changed', () => { + const areAllIdle = groups.reduce( + (prev, next) => prev && next.status === BenchmarkStatus.idle, + true + ); - 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 000000000000..4384b7660b74 --- /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 000000000000..81607364dc6f --- /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 000000000000..410556f21a01 --- /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 100644 index 000000000000..bae38ae1e166 --- /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 000000000000..188bc5dc5a81 --- /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 97c30edc5da6..200d135dff0a 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 26bce75205e0..7d46a7defb7d 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 @@ 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 52546042fb90..616cdab1e013 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 @@ 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 @@ public ComponentHubReliabilityTest(BasicTestAppServerSiteFixture } [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 76a01c13f3b3..609fd83f30a7 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs @@ -37,7 +37,7 @@ protected override void InitializeAsyncCore() [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 @@ public void CanSetCultureAndParseCultureInvariantNumbersAndDatesWithFormComponen 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 c3e0626eb666..ef66a592d3ad 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.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 @@ 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 @@ await Client.InvokeDotNetMethod( } [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 5e9e2ac4e981..3f4958bdc313 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/RemoteRendererBufferLimitTest.cs @@ -6,6 +6,7 @@ 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 @@ 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 @@ public async Task DispatchedEventsWillKeepBeingProcessed_ButUpdatedWillBeDelayed 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 25b7b552af34..de7a9321e99a 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/ServerComponentRenderingTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ServerComponentRenderingTest.cs @@ -6,8 +6,8 @@ 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 @@ public void ThrowsIfRenderIsRequestedOutsideSyncContext() $"{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 6d2e86057375..77b7da75d65a 100644 --- a/src/Components/test/E2ETest/Tests/ErrorNotificationServerSideTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ServerErrorNotificationTest.cs @@ -5,16 +5,15 @@ 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 40e9494a9b2b..70fc700e89f0 100644 --- a/src/Components/test/E2ETest/Tests/BindTest.cs +++ b/src/Components/test/E2ETest/Tests/BindTest.cs @@ -214,6 +214,11 @@ public void CanBindSelect() 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 @@ public void CanBindSelectToMarkup() // 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 @@ public void CanBindTextboxNullableLong() 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 fa93b857177a..372995c2f52f 100644 --- a/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs +++ b/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs @@ -12,6 +12,7 @@ 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 @@ public void CanDoubleDispatchRenderToSyncContext() } [Fact] - public void CanDispatchAsyncWorkToSyncContext() + public virtual void CanDispatchAsyncWorkToSyncContext() { var appElement = Browser.MountTestComponent(); var result = appElement.FindElement(By.Id("result")); @@ -626,7 +627,7 @@ public void CanPatchRenderTreeToMatchLatestDOMState() 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 @@ public void CanPatchRenderTreeToMatchLatestDOMState() }); // 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 7c0705acdefd..883d1bc5ab21 100644 --- a/src/Components/test/E2ETest/Tests/ErrorNotificationClientSideTest.cs +++ b/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs @@ -13,9 +13,9 @@ 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 5cbf1c3ac26b..d2fd55b195ab 100644 --- a/src/Components/test/E2ETest/Tests/EventTest.cs +++ b/src/Components/test/E2ETest/Tests/EventTest.cs @@ -167,7 +167,7 @@ public void PreventDefault_DotNotApplyByDefault() } [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 @@ public void InputEvent_RespondsOnKeystrokes_EvenIfUpdatesAreLaggy() // 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 edd7404f6fe0..6db798827153 100644 --- a/src/Components/test/E2ETest/Tests/FormsTest.cs +++ b/src/Components/test/E2ETest/Tests/FormsTest.cs @@ -42,7 +42,7 @@ protected virtual IWebElement MountTypicalValidationComponent() [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 1fb1b26ec903..b8db27fd2b21 100644 --- a/src/Components/test/E2ETest/Tests/MonoSanityTest.cs +++ b/src/Components/test/E2ETest/Tests/MonoSanityTest.cs @@ -74,14 +74,6 @@ public void CanReceiveDotNetExceptionInJavaScript() 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 652226bf26c0..f7187a45573e 100644 --- a/src/Components/test/E2ETest/Tests/PerformanceTest.cs +++ b/src/Components/test/E2ETest/Tests/PerformanceTest.cs @@ -13,11 +13,11 @@ 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 @@ public void BenchmarksRunWithoutError() () => 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 000000000000..75359253c0a5 --- /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 a84e769eb42b..8f9d6e2a127d 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 6f2b1cc3e3dd..937ef009e7fe 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 98357d0e8835..9914ec452179 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 71024bffb8cc..fafd59d49b0e 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 5294f2b1dfc7..04b93ea0d3e7 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 0548f382a898..eff29276a459 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 cebb226e7c46..2be7d81b4e2d 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 @@ public static void Main(string[] args) 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 13256fb7bc6a..77fbab5eb587 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 517d9d6fcc10..a37c08a7d1a2 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 777375d9e0e2..ea9900430b1c 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 d512e61f61c8..4eebc1f24b64 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 af2f28f65854..b0ba837af22b 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 c12abcf463e5..c97ca68bd06c 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 da18f3a2e57d..7628d8233da8 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 27842cb73b11..7ea486e4b2bb 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 a1f51b94f5f9..d639fb1e0ae6 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 ef85eb36574a..5e3b743ff689 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 12fe72720c7a..66bfdc777bb2 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 c15c2e155636..f875d35edbb2 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 96ac0383bea5..caf1ab80e607 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 1102d7952dfb..df7b62a1697b 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 ff8ed336b0f5..0ca933164a50 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 edbcd5873cad..c0f6adc2911b 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 1b4166341a93..543e0bdd5dd9 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 679e23789497..81aacfa7e7b4 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 ccc07b30f613..835d806c2ee6 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 3c76e6dc4552..ac49c1864181 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 8679ada1c62c..d846394fa943 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 892d6b9b5d92..5234625f8d2e 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 5ac63161670b..779cf7d82c17 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 6dbe6964a7c1..b326cdff079a 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 3c76e6dc4552..ac49c1864181 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 b0facf26da17..9c560017be7a 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 514e77d41b69..b3f4e81300f7 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 1e9cc2414b19..fa3775c56451 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 5849cee8bbe8..e8e0653f3ca7 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 07b982ad9e6d..fb376267c2c8 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/authorize.service.ts b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/api-authorization/authorize.service.ts index 63e7000069f3..bb4b0b09db5e 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 f90d0df3808e..8d312e32fdc2 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 78969d39bf0d..e99e88d19569 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 f66fa532038d..679b31ce3387 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 026a91a06293..37b350cc1c25 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 16317897b1c5..88492582fb73 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 75e26da4e954..cdcc89182a74 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 c6e9544e438c..4b2e6ad78671 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 000000000000..6ce384e5cedb --- /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/Login.js b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/api-authorization/Login.js index 7ef201c91cc7..3954657f30be 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 9c7ce60528c1..f32b4adac42d 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 000000000000..6ce384e5cedb --- /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 f5e132e0e245..40fe7d79bc6c 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 9fed8302888a..a0c1b7a1e5ae 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 5f7d5ff66109..d452be0ee3ff 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 80ddb46adb70..766c65df0f22 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 68c74c589d64..89bb95258c63 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 ba4180de9578..ec8993da7ea1 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 7d989b65a304..575036e9c7ac 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 6aeffd768763..d6859c6f7278 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 f77838b435af..ecba4fbd8fc1 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 972bfc4ead24..8308860edccf 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 4c01f11400f6..6100d7cacd13 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 7971ae01ac50..61ad47fbdcfd 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 94fd8d012b95..e6ff856e7e77 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 d13b7e452c8a..5ad25a16d6ec 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 1c6239d10ede..80201023862c 100644 --- a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs @@ -24,7 +24,8 @@ public BlazorServerTemplateTest(ProjectFactoryFixture projectFactory, BrowserFix public Project Project { get; private set; } - [Fact] + [ConditionalFact] + [SkipOnHelix("selenium")] public async Task BlazorServerTemplateWorks_NoAuth() { Project = await ProjectFactory.GetOrCreateProject("blazorservernoauth", Output); @@ -79,9 +80,10 @@ public async Task BlazorServerTemplateWorks_NoAuth() } } - [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 df7acd2f4ddd..76600bf9e287 100644 --- a/src/ProjectTemplates/test/ByteOrderMarkTest.cs +++ b/src/ProjectTemplates/test/ByteOrderMarkTest.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; +using Microsoft.AspNetCore.Testing; using Xunit; using Xunit.Abstractions; @@ -19,7 +20,8 @@ public ByteOrderMarkTest(ITestOutputHelper output) _output = output; } - [Theory] + [ConditionalTheory] + [SkipOnHelix("missing files")] [InlineData("Web.ProjectTemplates")] [InlineData("Web.Spa.ProjectTemplates")] public void JSAndJSONInAllTemplates_ShouldNotContainBOM(string projectName) @@ -60,7 +62,8 @@ public void JSAndJSONInAllTemplates_ShouldNotContainBOM(string projectName) 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 56e2e8c10539..a448f039a4d9 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,8 @@ public EmptyWebTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHel public ITestOutputHelper Output { get; } - [Fact] + [ConditionalFact] + [SkipOnHelix("Cert failures", Queues = "OSX.1014.Amd64;OSX.1014.Amd64.Open")] public async Task EmptyWebTemplateCSharp() { await EmtpyTemplateCore(languageOverride: null); @@ -41,6 +43,12 @@ private async Task EmtpyTemplateCore(string languageOverride) 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 4713a7a9c454..5104dc42b7b3 100644 --- a/src/ProjectTemplates/test/GrpcTemplateTest.cs +++ b/src/ProjectTemplates/test/GrpcTemplateTest.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; using Templates.Test.Helpers; using Xunit; using Xunit.Abstractions; @@ -23,7 +24,9 @@ public GrpcTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper 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() { Project = await ProjectFactory.GetOrCreateProject("grpc", Output); @@ -40,7 +43,7 @@ public async Task GrpcTemplate() using (var serverProcess = Project.StartBuiltProjectAsync()) { // 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 + // https://github.com/dotnet/aspnetcore/issues/11061 if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { Assert.True(serverProcess.Process.HasExited, "built"); @@ -64,7 +67,7 @@ public async Task GrpcTemplate() using (var aspNetProcess = Project.StartPublishedProjectAsync()) { // 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 + // https://github.com/dotnet/aspnetcore/issues/11061 if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { Assert.True(aspNetProcess.Process.HasExited, "published"); diff --git a/src/ProjectTemplates/test/Helpers/AspNetProcess.cs b/src/ProjectTemplates/test/Helpers/AspNetProcess.cs index c4415bf55f0d..86724b970fb9 100644 --- a/src/ProjectTemplates/test/Helpers/AspNetProcess.cs +++ b/src/ProjectTemplates/test/Helpers/AspNetProcess.cs @@ -11,6 +11,7 @@ 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.Abstractions; @@ -51,8 +52,7 @@ public AspNetProcess( Timeout = TimeSpan.FromMinutes(2) }; - var now = DateTimeOffset.Now; - new CertificateManager().EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1)); + EnsureDevelopmentCertificates(); output.WriteLine("Running ASP.NET application..."); @@ -64,6 +64,12 @@ public AspNetProcess( } } + 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 c63f008f831a..744ada299bfb 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 afb0783cbc4d..deb12fb16c65 100644 --- a/src/ProjectTemplates/test/Helpers/PageUrls.cs +++ b/src/ProjectTemplates/test/Helpers/PageUrls.cs @@ -12,6 +12,7 @@ public static class PageUrls 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 fc6923ae5c59..b16801a5f748 100644 --- a/src/ProjectTemplates/test/Helpers/Project.cs +++ b/src/ProjectTemplates/test/Helpers/Project.cs @@ -7,8 +7,10 @@ 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 Xunit; using Xunit.Abstractions; @@ -21,13 +23,18 @@ public class Project { 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 +42,16 @@ public class Project 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 +119,7 @@ internal async Task RunDotNetNewAsync( } } - 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 +130,7 @@ internal async Task RunDotNetPublishAsync(bool takeNodeLock = false, 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 +141,7 @@ internal async Task RunDotNetPublishAsync(bool takeNodeLock = false, } } - 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 +152,7 @@ internal async Task RunDotNetBuildAsync(bool takeNodeLock = false, ID 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; @@ -242,7 +251,7 @@ internal async Task RestoreWithRetryAsync(ITestOutputHelper output, s do { restoreResult = await RestoreAsync(output, workingDirectory); - if (restoreResult.ExitCode == 0) + if (restoreResult.HasExited && restoreResult.ExitCode == 0) { return restoreResult; } @@ -298,20 +307,24 @@ private async Task RestoreAsync(ITestOutputHelper output, string work 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 +336,24 @@ internal async Task RunDotNetEfCreateMigrationAsync(string migrationN 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 +541,18 @@ private void CaptureBinLogOnFailure(ProcessEx result) } 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 ffd1b0ae4ffc..9399433be966 100644 --- a/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs +++ b/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs @@ -61,9 +61,11 @@ public async Task GetOrCreateProject(string projectKey, ITestOutputHelp } 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 2c3425959208..5c94fde2b49b 100644 --- a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs +++ b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs @@ -8,6 +8,7 @@ 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 @@ internal static class TemplatePackageInstaller "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 @@ public static async Task RunDotNetNew(ITestOutputHelper output, strin 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 @@ private static async Task InstallTemplatePackages(ITestOutputHelper output) /* * 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 @@ NUnit 3 Test Item (nunit-test) F# 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 deede64521a5..da766229a6d2 100644 --- a/src/ProjectTemplates/test/IdentityUIPackageTest.cs +++ b/src/ProjectTemplates/test/IdentityUIPackageTest.cs @@ -5,6 +5,7 @@ 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 @@ public static TheoryData, string, string[]> MSBuildI "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 6186fc2f15a1..9990532b1d12 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 25fbc524a47e..1d3fb7bb2ad7 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 @@ private async Task MvcTemplateCore(string languageOverride) 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 @@ private async Task MvcTemplateCore(string languageOverride) } } - [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 @@ public async Task MvcTemplate_IndividualAuth(bool useLocalDB) PageUrls.PrivacyUrl, PageUrls.ForgotPassword, PageUrls.RegisterUrl, + PageUrls.ResendEmailConfirmation, PageUrls.ExternalArticle, PageUrls.PrivacyUrl } }, @@ -214,6 +224,7 @@ public async Task MvcTemplate_IndividualAuth(bool useLocalDB) } [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 5ed506cf28cf..8f1984b78a80 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 + - + + @@ -52,18 +55,22 @@ <_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 - + $([MSBuild]::NormalizePath('$(OutputPath)$(TestTemplateCreationFolder)')) @@ -81,7 +88,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 0d02a56f8f84..b16f8fa345ab 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 @@ public async Task RazorClassLibraryTemplate_WithViews_Async() } [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 3de64d6ea7a9..f7727fbb5af3 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -25,7 +25,8 @@ public RazorPagesTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputH 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 @@ public async Task RazorPagesTemplate_NoAuth() } } - [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 @@ public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB) PageUrls.PrivacyUrl, PageUrls.ForgotPassword, PageUrls.RegisterUrl, + PageUrls.ResendEmailConfirmation, PageUrls.ExternalArticle, PageUrls.PrivacyUrl } }, @@ -210,6 +214,7 @@ public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB) } [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 e1d5db13388e..fc7877518925 100644 --- a/src/ProjectTemplates/test/SpaTemplateTest/AngularTemplateTest.cs +++ b/src/ProjectTemplates/test/SpaTemplateTest/AngularTemplateTest.cs @@ -15,15 +15,18 @@ public class AngularTemplateTest : SpaTemplateTestBase 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 44d6b67f3256..3e32514cc1e8 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 @@ public ReactReduxTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixtu { } - [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 469e87acd560..d4a1ff175645 100644 --- a/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs +++ b/src/ProjectTemplates/test/SpaTemplateTest/ReactTemplateTest.cs @@ -17,15 +17,19 @@ public ReactTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture br { } - [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 130533bf152f..d702e01be1e6 100644 --- a/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs +++ b/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs @@ -10,6 +10,7 @@ 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 @@ private void TestBasicNavigation(bool visitFetchData, bool usesAuth, IWebDriver 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 89d047a06e63..2f9a5ce16240 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 @@ public WebApiTemplateTest(ProjectFactoryFixture factoryFixture, ITestOutputHelpe [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 @@ private async Task WebApiTemplateCore(string languageOverride) 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 738eafc61d6a..09f444811cf8 100644 --- a/src/ProjectTemplates/test/WorkerTemplateTest.cs +++ b/src/ProjectTemplates/test/WorkerTemplateTest.cs @@ -5,6 +5,7 @@ using Templates.Test.Helpers; using Xunit; using Xunit.Abstractions; +using Microsoft.AspNetCore.Testing; namespace Templates.Test { @@ -20,7 +21,8 @@ public WorkerTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelpe 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 3a1ea190f601..c48c6bd8a461 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 9152fc62d0e7..5d70579fb6ff 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 000000000000..dfb6dacb887c --- /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 56b3976d61ac..000000000000 --- 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 06658a3da099..a178a077b815 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 9e2eb73d09b1..c8bd4d24f662 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 @@ protected RazorCompiledItem() { } 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 @@ public RazorCompiledItemLoader() { } 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 @@ public partial class TagHelperExecutionContext { 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 bfd4736d1d40..891462486402 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 36d08aa6aec6..de54d56596b6 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 @@ public sealed partial class HtmlAttributeNameAttribute : System.Attribute 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 @@ public sealed partial class HtmlTargetElementAttribute : System.Attribute 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 @@ public partial interface ITagHelperComponent 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 @@ public override void Encode(System.IO.TextWriter output, string value, int start 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 @@ public ReadOnlyTagHelperAttributeList(System.Collections.Generic.IList 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 @@ public partial class TagHelperAttribute : Microsoft.AspNetCore.Html.IHtmlContent 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 @@ public partial class TagHelperContext 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 9f8d7443c4a8..482bcbeeaef8 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 68a7abdde0a2..a33b05ceb873 100644 --- a/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs +++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs @@ -167,6 +167,15 @@ private X509ChainPolicy BuildChainPolicy(X509Certificate2 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 1b8eebfa6feb..70789514ac3e 100644 --- a/src/Security/Authentication/Certificate/src/CertificateAuthenticationOptions.cs +++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationOptions.cs @@ -15,10 +15,21 @@ public class CertificateAuthenticationOptions : AuthenticationSchemeOptions /// 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 cffabda4350d..86af316bad59 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 3af15e9d0652..000000000000 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 542131fdf15e..a589666e0a25 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 d9457f386182..f3d730a1f99a 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 @@ public partial class ChunkingCookieManager : Microsoft.AspNetCore.Authentication { 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 @@ public static partial class CookieAuthenticationDefaults 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 @@ public partial class CookieAuthenticationHandler : Microsoft.AspNetCore.Authenti 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 @@ public partial class CookieSignedInContext : Microsoft.AspNetCore.Authentication 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 a6bb4e7d1c60..a751c3eb53c6 100644 --- a/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs +++ b/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs @@ -9,7 +9,7 @@ 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 @@ public class CookieAuthenticationEvents 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 fc4edaa3ddfe..0248669979dc 100644 --- a/src/Security/Authentication/Cookies/src/CookieAuthenticationOptions.cs +++ b/src/Security/Authentication/Cookies/src/CookieAuthenticationOptions.cs @@ -39,27 +39,27 @@ public CookieAuthenticationOptions() /// 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 @@ public CookieBuilder Cookie /// /// 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 @@ public CookieBuilder Cookie /// /// 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 @@ public CookieBuilder Cookie /// 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 ac15a1853d2f..1a1d21a7741a 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 @@ public AuthenticationBuilder(Microsoft.Extensions.DependencyInjection.IServiceCo { 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 @@ protected AuthenticationHandler(Microsoft.Extensions.Options.IOptionsMonitor 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 @@ public static partial class Base64UrlTextEncoder 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 @@ public PolicySchemeOptions() { } 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 @@ public partial class PropertiesDataFormat : 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 @@ public virtual void Write(System.IO.BinaryWriter writer, Microsoft.AspNetCore.Au 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 @@ public void Success() { } 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 @@ protected virtual void GenerateCorrelationId(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 @@ public partial class TicketDataFormat : Microsoft.AspNetCore.Authentication.Secu 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 a8975f11a027..b4d4cbef8830 100644 --- a/src/Security/Authentication/Core/src/RemoteAuthenticationHandler.cs +++ b/src/Security/Authentication/Core/src/RemoteAuthenticationHandler.cs @@ -18,8 +18,6 @@ public abstract class RemoteAuthenticationHandler : AuthenticationHand 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 @@ protected virtual void GenerateCorrelationId(AuthenticationProperties properties } 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 19fbb851d77e..4977e1892b46 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 96bc1b8b33aa..774fa9cac04b 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 65a3e40f7aad..4f8831b29eb0 100644 --- a/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs +++ b/src/Security/Authentication/JwtBearer/samples/JwtBearerSample/Startup.cs @@ -78,7 +78,7 @@ public void Configure(IApplicationBuilder app) 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 f0e7cbc5dee5..758added5ba0 100644 --- a/src/Security/Authentication/JwtBearer/src/JwtBearerOptions.cs +++ b/src/Security/Authentication/JwtBearer/src/JwtBearerOptions.cs @@ -111,5 +111,15 @@ public class JwtBearerOptions : AuthenticationSchemeOptions /// 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 8829bfac0ffa..f52cfb2353cb 100644 --- a/src/Security/Authentication/JwtBearer/src/JwtBearerPostConfigureOptions.cs +++ b/src/Security/Authentication/JwtBearer/src/JwtBearerPostConfigureOptions.cs @@ -55,7 +55,11 @@ public void PostConfigure(string name, JwtBearerOptions options) 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 45391ac2db87..efee848b372d 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 8f7ee4dc442d..0f2dc832cce1 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 6114b15394e8..043900b1aab8 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 @@ protected override string BuildChallengeUrl(AuthenticationProperties properties, 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 8625e3f093e8..4e9737b50942 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs +++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs @@ -4,7 +4,7 @@ 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 d64c8da43e82..4ea083c94db2 100644 --- a/src/Security/Authentication/Negotiate/src/Events/AuthenticationFailedContext.cs +++ b/src/Security/Authentication/Negotiate/src/Events/AuthenticationFailedContext.cs @@ -24,7 +24,7 @@ public AuthenticationFailedContext( : 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 fb7a6a3a9f35..0b827e9dc35b 100644 --- a/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs +++ b/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs @@ -123,7 +123,7 @@ private byte[] GetOutgoingBlob(byte[] incomingBlob, out BlobErrorType status, ou 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 cd7ef7ef557a..390c449d9fd9 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 c880fb15647a..9392d1028ada 100644 --- a/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs +++ b/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs @@ -57,7 +57,7 @@ public NegotiateHandler(IOptionsMonitor options, ILoggerFactor /// 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 @@ public async Task HandleRequestAsync() _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 @@ protected override async Task HandleAuthenticateAsync() 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 e263a2c5f7b1..ec83949331b2 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 6c3baef3200a..bc18f861b5be 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.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 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate { + [QuarantinedTest] public class EventTests { [Fact] @@ -131,7 +133,7 @@ public async Task OnAuthenticationFailed_FromOtherBlobError_Fires() { 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 @@ public async Task OnAuthenticationFailed_FromOtherBlobError_Fires() 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 @@ public async Task OnAuthenticationFailed_FromCredentialError_Fires() { 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 @@ public async Task OnAuthenticationFailed_FromClientError_Fires() { 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 @@ public string GetOutgoingBlob(string incomingBlob, out BlobErrorType errorType, 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 7af38dfa4a42..3f483fc9e1d2 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 d696cd0afd34..89685b286b13 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.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 @@ namespace Microsoft.AspNetCore.Authentication.Negotiate { + [QuarantinedTest] public class NegotiateHandlerTests { [Fact] @@ -301,7 +303,7 @@ public async Task OtherError_Throws() 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 @@ public string GetOutgoingBlob(string incomingBlob, out BlobErrorType errorType, 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 eba5c12fea86..e5cf8ca7b630 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.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 @@ 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 73d5e441da30..2301daf1ad26 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 @@ public virtual void SetScope(params string[] scopes) { } 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 @@ public partial class OAuthCreatingTicketContext : Microsoft.AspNetCore.Authentic { 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 @@ public static partial class OAuthDefaults 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 @@ public OAuthEvents() { } 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 @@ public void Remove(string claimType) { } 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 @@ public override void Run(System.Text.Json.JsonElement userData, System.Security. 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 a6bfbbc550b1..29ef3036f8d6 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 @@ protected virtual string BuildChallengeUrl(AuthenticationProperties properties, 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 f1828659498e..78e9863f23ae 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 @@ await WriteHtmlAsync(context.Response, async response => } 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 5d4a94ec56da..c0696ba1a25a 100644 --- a/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs +++ b/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs @@ -37,18 +37,56 @@ private void CheckSameSite(HttpContext httpContext, CookieOptions options) { 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 79b6de0fbf78..8a6c82928d97 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 65ad366a506e..f4767f1e87ac 100644 --- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs +++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs @@ -34,8 +34,6 @@ public class OpenIdConnectHandler : RemoteAuthenticationHandler Options.Backchannel; @@ -78,13 +76,13 @@ protected virtual async Task HandleRemoteSignOutAsync() { 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 @@ private async Task HandleChallengeAsyncInternal(AuthenticationProperties propert 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 @@ protected override async Task HandleRemoteAuthenticateAsync 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 @@ protected override async Task HandleRemoteAuthenticateAsync } } // 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 @@ protected virtual async Task GetUserInformationAsync( 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 7e3366badaa6..1b86c8190125 100644 --- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs +++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs @@ -288,7 +288,7 @@ public override void Validate() /// 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 @@ public override CookieOptions Build(HttpContext context, DateTimeOffset expiresF 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 b79f1d1edf2c..f1a39d70815c 100644 --- a/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectPostConfigureOptions.cs +++ b/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectPostConfigureOptions.cs @@ -93,7 +93,11 @@ public void PostConfigure(string name, OpenIdConnectOptions options) } 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 51f01cbd3f7b..c59a0fb276bb 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 e963d7a27e88..5f09e608b956 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 @@ private async Task RetrieveUserDetailsAsync(AccessToken accessToke 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 79ea913ae1d2..7d1a23fabde2 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 27bb332ccf52..41b9509f578f 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 @@ public WsFederationHandler(IOptionsMonitor options, ILogger /// /// 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 fc4d189a1f3f..bd395c33ff5e 100644 --- a/src/Security/Authentication/test/CertificateTests.cs +++ b/src/Security/Authentication/test/CertificateTests.cs @@ -48,7 +48,7 @@ public async Task VerifyValidSelfSignedWithClientEkuAuthenticates() new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithClientEku); @@ -63,7 +63,7 @@ public async Task VerifyValidSelfSignedWithNoEkuAuthenticates() new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithNoEku); @@ -92,7 +92,7 @@ public async Task VerifyValidSelfSignedWithNoEkuFailsWhenSelfSignedCertsNotAllow new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.Chained, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithNoEku); @@ -107,7 +107,7 @@ public async Task VerifyValidSelfSignedWithServerFailsEvenIfSelfSignedCertsAreAl new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithServerEku); @@ -123,7 +123,7 @@ public async Task VerifyValidSelfSignedWithServerPassesWhenSelfSignedCertsAreAll { AllowedCertificateTypes = CertificateTypes.SelfSigned, ValidateCertificateUse = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithServerEku); @@ -139,7 +139,7 @@ public async Task VerifyValidSelfSignedWithServerFailsPurposeValidationIsOffButS { AllowedCertificateTypes = CertificateTypes.Chained, ValidateCertificateUse = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedValidWithServerEku); @@ -155,7 +155,7 @@ public async Task VerifyExpiredSelfSignedFails() { AllowedCertificateTypes = CertificateTypes.SelfSigned, ValidateCertificateUse = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedExpired); @@ -171,7 +171,7 @@ public async Task VerifyExpiredSelfSignedPassesIfDateRangeValidationIsDisabled() { AllowedCertificateTypes = CertificateTypes.SelfSigned, ValidateValidityPeriod = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedExpired); @@ -187,7 +187,7 @@ public async Task VerifyNotYetValidSelfSignedFails() { AllowedCertificateTypes = CertificateTypes.SelfSigned, ValidateCertificateUse = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedNotYetValid); @@ -203,7 +203,7 @@ public async Task VerifyNotYetValidSelfSignedPassesIfDateRangeValidationIsDisabl { AllowedCertificateTypes = CertificateTypes.SelfSigned, ValidateValidityPeriod = false, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, Certificates.SelfSignedNotYetValid); @@ -248,13 +248,58 @@ public async Task VerifyNotSendingACertificateEndsUpInForbidden() 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 @@ public async Task VerifyHeaderIsUsedIfCertIsNotPresent() 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 @@ public async Task VerifyHeaderEncodedCertFailsOnBadEncoding() var server = CreateServer( new CertificateAuthenticationOptions { - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, wireUpHeaderMiddleware: true); @@ -295,7 +340,7 @@ public async Task VerifySettingTheAzureHeaderOnTheForwarderOptionsWorks() new CertificateAuthenticationOptions { AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, wireUpHeaderMiddleware: true, headerName: "X-ARR-ClientCert"); @@ -312,7 +357,7 @@ public async Task VerifyACustomHeaderFailsIfTheHeaderIsNotPresent() var server = CreateServer( new CertificateAuthenticationOptions { - Events = sucessfulValidationEvents + Events = successfulValidationEvents }, wireUpHeaderMiddleware: true, headerName: "X-ARR-ClientCert"); @@ -534,11 +579,13 @@ private static TestServer CreateServer( { 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 @@ private static TestServer CreateServer( return server; } - private CertificateAuthenticationEvents sucessfulValidationEvents = new CertificateAuthenticationEvents() + private CertificateAuthenticationEvents successfulValidationEvents = new CertificateAuthenticationEvents() { OnCertificateValidated = context => { @@ -599,6 +646,15 @@ private static TestServer CreateServer( 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 @@ private static string GetFullyQualifiedFilePath(string filename) } } } + diff --git a/src/Security/Authentication/test/SharedAuthenticationTests.cs b/src/Security/Authentication/test/SharedAuthenticationTests.cs index 36956f8374bb..834efeaeddfe 100644 --- a/src/Security/Authentication/test/SharedAuthenticationTests.cs +++ b/src/Security/Authentication/test/SharedAuthenticationTests.cs @@ -203,6 +203,46 @@ public async Task ForwardForbidWinsOverDefault() 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 000000000000..a7420c8493b0 --- /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 000000000000..a8034b05a8b9 --- /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 000000000000..ef31f31a98da --- /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 b0cf327ff464..b4543827dab2 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 ab30a7c6d59b..b3141c497918 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 @@ public AllowAnonymousAttribute() { } 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 @@ public AuthorizationHandlerContext(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 @@ protected AuthorizationHandler() { } 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 @@ public void AddPolicy(string name, System.Action 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 @@ public partial class AuthorizationPolicyBuilder { 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 @@ public AuthorizationPolicyBuilder(params string[] authenticationSchemes) { } 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 @@ public partial class AuthorizeAttribute : System.Attribute, Microsoft.AspNetCore { 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 @@ public partial class AssertionRequirement : Microsoft.AspNetCore.Authorization.I { 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 @@ public PassThroughAuthorizationHandler() { } 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 ab30a7c6d59b..b3141c497918 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 @@ public AllowAnonymousAttribute() { } 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 @@ public AuthorizationHandlerContext(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 @@ protected AuthorizationHandler() { } 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 @@ public void AddPolicy(string name, System.Action 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 @@ public partial class AuthorizationPolicyBuilder { 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 @@ public AuthorizationPolicyBuilder(params string[] authenticationSchemes) { } 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 @@ public partial class AuthorizeAttribute : System.Attribute, Microsoft.AspNetCore { 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 @@ public partial class AssertionRequirement : Microsoft.AspNetCore.Authorization.I { 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 @@ public PassThroughAuthorizationHandler() { } 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 5fa452b733f2..0c31f5710540 100644 --- a/src/Security/Authorization/Core/src/AssertionRequirement.cs +++ b/src/Security/Authorization/Core/src/AssertionRequirement.cs @@ -56,5 +56,10 @@ public async Task HandleAsync(AuthorizationHandlerContext context) 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 f1833da95496..b7c52722fabe 100644 --- a/src/Security/Authorization/Core/src/AuthorizationPolicy.cs +++ b/src/Security/Authorization/Core/src/AuthorizationPolicy.cs @@ -37,7 +37,7 @@ public AuthorizationPolicy(IEnumerable requirements, throw new ArgumentNullException(nameof(authenticationSchemes)); } - if (requirements.Count() == 0) + if (!requirements.Any()) { throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty); } @@ -150,7 +150,7 @@ public static async Task CombineAsync(IAuthorizationPolicyP } 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 @@ public static async Task CombineAsync(IAuthorizationPolicyP } 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 2f483e37ce40..ffcc339aae0a 100644 --- a/src/Security/Authorization/Core/src/AuthorizationPolicyBuilder.cs +++ b/src/Security/Authorization/Core/src/AuthorizationPolicyBuilder.cs @@ -130,7 +130,7 @@ public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable /// 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 0f788cd5ad7a..57fcf253b769 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 @@ public static IServiceCollection AddAuthorizationCore(this IServiceCollection se { 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 @@ public static IServiceCollection AddAuthorizationCore(this IServiceCollection se } /// - /// 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 93b1deea6d11..f77b74fd7499 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 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte } 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 bc5d571c47bc..475dea33304d 100644 --- a/src/Security/Authorization/Core/src/DefaultAuthorizationService.cs +++ b/src/Security/Authorization/Core/src/DefaultAuthorizationService.cs @@ -102,7 +102,7 @@ public async Task AuthorizeAsync(ClaimsPrincipal user, obje } else { - _logger.UserAuthorizationFailed(); + _logger.UserAuthorizationFailed(result.Failure); } return result; } @@ -132,4 +132,4 @@ public async Task AuthorizeAsync(ClaimsPrincipal user, obje 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 e88cce7aacd0..35828735a5b0 100644 --- a/src/Security/Authorization/Core/src/DenyAnonymousAuthorizationRequirement.cs +++ b/src/Security/Authorization/Core/src/DenyAnonymousAuthorizationRequirement.cs @@ -29,5 +29,10 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte } 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 e31d88161df2..ef57f8c09fbd 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 @@ static LoggingExtensions() 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 02ab946fade0..36cee10aac63 100644 --- a/src/Security/Authorization/Core/src/NameAuthorizationRequirement.cs +++ b/src/Security/Authorization/Core/src/NameAuthorizationRequirement.cs @@ -48,5 +48,10 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte } 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 c3f16356d36f..0e2a6fcf3fe3 100644 --- a/src/Security/Authorization/Core/src/OperationAuthorizationRequirement.cs +++ b/src/Security/Authorization/Core/src/OperationAuthorizationRequirement.cs @@ -13,5 +13,10 @@ public class OperationAuthorizationRequirement : IAuthorizationRequirement /// 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 811e17aacda5..e5a2251a146f 100644 --- a/src/Security/Authorization/Core/src/RolesAuthorizationRequirement.cs +++ b/src/Security/Authorization/Core/src/RolesAuthorizationRequirement.cs @@ -25,7 +25,7 @@ public RolesAuthorizationRequirement(IEnumerable allowedRoles) throw new ArgumentNullException(nameof(allowedRoles)); } - if (allowedRoles.Count() == 0) + if (!allowedRoles.Any()) { throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty); } @@ -64,5 +64,11 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte 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 571d21a03d0c..138cd20ea55c 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 @@ public partial interface IPolicyEvaluator 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 686374d829c2..af115e9daaa7 100644 --- a/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs +++ b/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs @@ -68,7 +68,7 @@ public async Task Invoke(HttpContext context) if (authorizeResult.Challenged) { - if (policy.AuthenticationSchemes.Any()) + if (policy.AuthenticationSchemes.Count > 0) { foreach (var scheme in policy.AuthenticationSchemes) { @@ -84,7 +84,7 @@ public async Task Invoke(HttpContext context) } 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 000000000000..d19786032951 --- /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 86858f4fe126..075a6ee65597 100644 --- a/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs +++ b/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs @@ -53,7 +53,7 @@ public async Task NoEndpointWithFallback_AnonymousUser_Challenges() // Assert Assert.False(next.Called); } - + [Fact] public async Task HasEndpointWithoutAuth_AnonymousUser_Allows() { @@ -135,6 +135,27 @@ public async Task HasEndpointWithAuth_AnonymousUser_Challenges() 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 000000000000..d1bbe69a6443 --- /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 d0fe9a62e94e..ecd55a4acc14 100644 --- a/src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs +++ b/src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs @@ -8,6 +8,7 @@ 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 @@ public async Task Authorize_ShouldAllowIfClaimIsPresentWithSpecifiedAuthType() { services.AddAuthorization(options => { - options.AddPolicy("Basic", policy => { + options.AddPolicy("Basic", policy => + { policy.AddAuthenticationSchemes("Basic"); policy.RequireClaim("Permission", "CanViewPage"); }); @@ -710,7 +712,8 @@ public PassThroughRequirement(bool succeed) protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PassThroughRequirement requirement) { - if (Succeed) { + if (Succeed) + { context.Succeed(requirement); } return Task.FromResult(0); @@ -926,7 +929,6 @@ public async Task CanUseValueTypeResource() Assert.True((await authorizationService.AuthorizeAsync(user, 2, Operations.Edit)).Succeeded); } - [Fact] public async Task DoesNotCallHandlerWithWrongResourceType() { @@ -1174,5 +1176,104 @@ public async Task CanUseCustomHandlerProvider() 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 000000000000..3d3563475821 --- /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 000000000000..73a9a98144af --- /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 000000000000..623d01c98e04 --- /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 000000000000..1b8d5b09c767 --- /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 f2e231debcad..bd378bd871c8 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 @@ public static partial class CookiePolicyAppBuilderExtensions 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 098bd3348395..56e5998808ad 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 2d03c65c3df6..2c1b46264e29 100644 --- a/src/Security/CookiePolicy/src/ResponseCookiesWrapper.cs +++ b/src/Security/CookiePolicy/src/ResponseCookiesWrapper.cs @@ -115,8 +115,7 @@ public string CreateConsentCookie() 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 0ba28c1e97ca..5ed702c4f286 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 93250dc8a4fd..32566c71432a 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 c6bb27153640..65871e5a7837 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 000000000000..7046bb98a0fc --- /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 8d0e937b34e6..a29b2bbc9261 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 @@ public async Task OnPostAsync() 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 7a9141075de6..70205c036708 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 9808064e241e..1753f9f54888 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 955a91148634..e216b7d19b96 100644 --- a/src/Security/test/AuthSamples.FunctionalTests/IdentityExternalClaimsTests.cs +++ b/src/Security/test/AuthSamples.FunctionalTests/IdentityExternalClaimsTests.cs @@ -29,7 +29,7 @@ public async Task DefaultReturns200() 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 c6a77eb8ef10..262c012733c2 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 dbd0a70349fb..75ed809b6d97 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 @@ public partial class AddressInUseException : System.InvalidOperationException 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 @@ public ConnectionAbortedException(string message, System.Exception inner) { } 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 @@ public static partial class ConnectionBuilderExtensions 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 @@ public partial class ConnectionItems : System.Collections.Generic.ICollection 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 @@ public partial class DefaultConnectionContext : 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 @@ public partial interface IConnectionListenerFactory { 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 @@ public enum TransferFormat 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 @@ public partial interface IMemoryPoolFeature { 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 dbd0a70349fb..75ed809b6d97 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 @@ public partial class AddressInUseException : System.InvalidOperationException 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 @@ public ConnectionAbortedException(string message, System.Exception inner) { } 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 @@ public static partial class ConnectionBuilderExtensions 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 @@ public partial class ConnectionItems : System.Collections.Generic.ICollection 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 @@ public partial class DefaultConnectionContext : 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 @@ public partial interface IConnectionListenerFactory { 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 @@ public enum TransferFormat 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 @@ public partial interface IMemoryPoolFeature { 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 dbd0a70349fb..75ed809b6d97 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 @@ public partial class AddressInUseException : System.InvalidOperationException 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 @@ public ConnectionAbortedException(string message, System.Exception inner) { } 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 @@ public static partial class ConnectionBuilderExtensions 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 @@ public partial class ConnectionItems : System.Collections.Generic.ICollection 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 @@ public partial class DefaultConnectionContext : 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 @@ public partial interface IConnectionListenerFactory { 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 @@ public enum TransferFormat 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 @@ public partial interface IMemoryPoolFeature { 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 000000000000..662b8c902e9b --- /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 100917b0094a..55b0311eb977 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs @@ -40,4 +40,4 @@ public static IConnectionBuilder Run(this IConnectionBuilder connectionBuilder, }); } } -} \ 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 947066ca796f..02b291c2c816 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 @@ public virtual void Abort(ConnectionAbortedException abortReason) /// /// 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 e9e208d61a65..9bc8ab29024c 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionHandler.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionHandler.cs @@ -6,15 +6,15 @@ 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 000000000000..c47a2485b86d --- /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 000000000000..66f706dbf912 --- /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 000000000000..f86f2ee61e6e --- /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 000000000000..d867fe0938de --- /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 000000000000..8f3caf34db54 --- /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 000000000000..a3f69f7a6854 --- /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 000000000000..3b5010beda33 --- /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 d7040f24456f..ddefc4f62c57 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 000000000000..202f29df5eb5 --- /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 000000000000..ce0850d2816f --- /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 000000000000..c85298ea2d9b --- /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 0321d5af2d15..7000b86611e8 100644 --- a/src/Servers/Connections.Abstractions/src/UriEndPoint.cs +++ b/src/Servers/Connections.Abstractions/src/UriEndPoint.cs @@ -24,5 +24,7 @@ public UriEndPoint(Uri uri) /// 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 bdd6b9c522d5..75f0aad286c0 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 31f097495b1e..98e383a676de 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 @@ public sealed partial class AuthenticationManager { 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 @@ internal HttpSysException() { } 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 @@ internal TimeoutManager() { } 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 000000000000..df39ba8da81b --- /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 35450964c9ef..a824a8daee00 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 42f6d11f81e5..28665f54dc48 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 @@ public void Configure(IApplicationBuilder app) 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 2d2a12d81be1..000000000000 --- 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 f57945de4250..f81dc83833a7 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 @@ public static void Main(string[] args) 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 249372ae2d2b..000000000000 --- 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 c92be92bc82e..6d5bb6e3384b 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 - + TestClient.Program - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - + - + - - - - \ No newline at end of file + + diff --git a/src/Servers/HttpSys/src/AsyncAcceptContext.cs b/src/Servers/HttpSys/src/AsyncAcceptContext.cs index 4c0cd867563c..59083225986d 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 @@ internal unsafe class AsyncAcceptContext : IAsyncResult, IDisposable 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 @@ internal void AllocateNativeRequest(uint? size = null, ulong requestId = 0) { _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)); - - 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; + boundHandle.AllocateNativeOverlapped(IOCallback, this, pinData: null)); - 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 29f5a3495aec..9b54f0ab0f43 100644 --- a/src/Servers/HttpSys/src/AuthenticationManager.cs +++ b/src/Servers/HttpSys/src/AuthenticationManager.cs @@ -45,12 +45,22 @@ public AuthenticationSchemes Schemes } } + /// + /// 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 7cce609f3e71..23e174344ce3 100644 --- a/src/Servers/HttpSys/src/FeatureContext.cs +++ b/src/Servers/HttpSys/src/FeatureContext.cs @@ -35,7 +35,9 @@ internal class FeatureContext : IHttpRequestIdentifierFeature, IHttpMaxRequestBodySizeFeature, IHttpBodyControlFeature, - IHttpSysRequestInfoFeature + IHttpSysRequestInfoFeature, + IHttpResponseTrailersFeature, + IHttpResetFeature { private RequestContext _requestContext; private IFeatureCollection _features; @@ -63,6 +65,7 @@ internal class FeatureContext : private PipeWriter _pipeWriter; private bool _bodyCompleted; private IHeaderDictionary _responseHeaders; + private IHeaderDictionary _responseTrailers; private Fields _initializedFields; @@ -85,7 +88,11 @@ internal FeatureContext(RequestContext requestContext) _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 @@ string IHttpRequestFeature.Protocol { 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 @@ internal ITlsHandshakeFeature GetTlsHandshakeFeature() 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 @@ IHeaderDictionary IHttpResponseFeature.Headers set { _responseHeaders = value; } } - bool IHttpResponseFeature.HasStarted => Response.HasStarted; + bool IHttpResponseFeature.HasStarted => _responseStarted; void IHttpResponseFeature.OnStarting(Func callback, object state) { @@ -456,6 +465,12 @@ Task IHttpResponseBodyFeature.StartAsync(CancellationToken cancellation) Task IHttpResponseBodyFeature.CompleteAsync() => CompleteAsync(); + void IHttpResetFeature.Reset(int errorCode) + { + _requestContext.SetResetCode(errorCode); + _requestContext.Abort(); + } + internal async Task CompleteAsync() { if (!_responseStarted) @@ -559,6 +574,12 @@ bool IHttpBodyControlFeature.AllowSynchronousIO 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 24f6c61955ad..224201d79fa2 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 @@ internal class HttpSysListener : IDisposable // 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 @@ public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory) Options = options; - Logger = LogHelper.CreateLogger(loggerFactory, typeof(HttpSysListener)); + Logger = loggerFactory.CreateLogger(); _state = State.Stopped; _internalLock = new object(); @@ -89,7 +92,7 @@ public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory) _requestQueue?.Dispose(); _urlGroup?.Dispose(); _serverSession?.Dispose(); - LogHelper.LogException(Logger, ".Ctor", exception); + Logger.LogError(LoggerEventIds.HttpSysListenerCtorError, exception, ".Ctor"); throw; } } @@ -132,7 +135,7 @@ public void Start() { 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 @@ public void Start() // 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 @@ private void Stop() 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 @@ private void Stop() } catch (Exception exception) { - LogHelper.LogException(Logger, "Stop", exception); + Logger.LogError(LoggerEventIds.ListenerStopError, exception, "Stop"); throw; } } @@ -233,14 +238,14 @@ private void Dispose(bool disposing) { 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 @@ public Task AcceptAsync() } 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 2a9345d62424..000000000000 --- 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 000000000000..f10096e1b01b --- /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 9917e6987284..f5e90a987611 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 @@ public MessagePump(IOptions options, ILoggerFactory loggerFactor } _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 @@ public Task StartAsync(IHttpApplication application, Cancell } 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 @@ public Task StartAsync(IHttpApplication application, Cancell 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 @@ private void ActivateRequestProcessingLimits() } } + 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 @@ private async void ProcessRequestsWorker() 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 @@ private async void ProcessRequestsWorker() { // 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 @@ private async void ProcessRequestAsync(object requestContextObj) } 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 @@ private async void ProcessRequestAsync(object requestContextObj) { 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 @@ void RegisterCancelation() { 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 @@ void RegisterCancelation() // 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 06d5ad8b82c6..9877d269859d 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 ecef12b98915..d624808d0414 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 @@ internal CancellationToken GetTokenForConnection(ulong connectionId) } 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 @@ private ConnectionCancellation GetCreatedConnectionCancellation(ulong connection { // 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 @@ private unsafe CancellationToken CreateDisconnectToken(ulong connectionId) 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 @@ private unsafe CancellationToken CreateDisconnectToken(ulong connectionId) } catch (AggregateException exception) { - LogHelper.LogException(_logger, "CreateDisconnectToken Callback", exception); + _logger.LogError(LoggerEventIds.DisconnectHandlerError, exception, "CreateDisconnectToken Callback"); } }, null, null)); @@ -98,7 +98,7 @@ private unsafe CancellationToken CreateDisconnectToken(ulong connectionId) 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 @@ private unsafe CancellationToken CreateDisconnectToken(ulong connectionId) 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 c4e23d3b3dcc..6781465bc24f 100644 --- a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs +++ b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs @@ -70,6 +70,7 @@ internal static extern unsafe uint HttpCreateRequestQueue(HTTPAPI_VERSION versio [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 @@ internal static HTTP_API_VERSION ApiVersion } } + 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 @@ private static void InitHttpApi(ushort majorVersion, ushort minorVersion) 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 00ecc78de9e3..4fb2d602808a 100644 --- a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs +++ b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs @@ -83,7 +83,7 @@ internal RequestQueue(UrlGroup urlGroup, string requestQueueName, RequestQueueMo 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 000000000000..bf3254c6d68b --- /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 8f33c7b6784b..6600cffe607a 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 @@ internal unsafe void SetMaxConnections(long maxConnections) } 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 @@ internal void SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY property, IntPtr inf 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 @@ internal void SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY property, IntPtr inf 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 @@ internal void RegisterPrefix(string uriPrefix, int contextId) } } } - + 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 @@ public void Dispose() 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 74d7ed902f53..8f9bd8132e03 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/ClientCertLoader.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/ClientCertLoader.cs @@ -400,13 +400,13 @@ internal static unsafe ChannelBinding GetChannelBindingFromTls(RequestQueue requ } 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 049789abea38..84fb0173933b 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs @@ -293,7 +293,7 @@ private void GetTlsHandshakeResults() 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 @@ public X509Certificate2 ClientCertificate } 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 89405cf5389b..605288b39760 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 @@ internal bool TryGetChannelBinding(ref ChannelBinding value) { 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 @@ public void Abort() } catch (Exception ex) { - LogHelper.LogDebug(Logger, "Abort", ex); + Logger.LogDebug(LoggerEventIds.AbortError, ex, "Abort"); } _requestAbortSource.Dispose(); } @@ -218,5 +219,25 @@ internal void ForceCancelRequest() // 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 f0bf45d68f62..758a060b941f 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/RequestStream.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/RequestStream.cs @@ -165,7 +165,7 @@ public override unsafe int Read([In, Out] byte[] buffer, int offset, int size) 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 @@ public override unsafe IAsyncResult BeginRead(byte[] buffer, int offset, int siz } catch (Exception e) { - LogHelper.LogException(Logger, "BeginRead", e); + Logger.LogError(LoggerEventIds.ErrorWhenReadBegun, e, "BeginRead"); asyncResult.Dispose(); throw; } @@ -258,7 +258,7 @@ public override unsafe IAsyncResult BeginRead(byte[] buffer, int offset, int siz 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 @@ public override unsafe Task ReadAsync(byte[] buffer, int offset, int size, { asyncResult.Dispose(); Abort(); - LogHelper.LogException(Logger, "ReadAsync", e); + Logger.LogError(LoggerEventIds.ErrorWhenReadAsync, e, "ReadAsync"); throw; } @@ -386,7 +386,7 @@ public override unsafe Task ReadAsync(byte[] buffer, int offset, int size, 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 5f29763d2ff5..3f8dd86f7e7a 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Response.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Response.cs @@ -5,6 +5,7 @@ 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 @@ internal sealed class Response private long _expectedBodyLength; private BoundaryType _boundaryType; private HttpApiTypes.HTTP_RESPONSE_V2 _nativeResponse; + private HeaderCollection _trailers; internal Response(RequestContext requestContext) { @@ -142,6 +144,16 @@ private static bool CanSendResponseBody(int responseCode) 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 @@ public TimeSpan? CacheTtl } } + // 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 @@ internal HttpApiTypes.HTTP_FLAGS ComputeHeaders(long writeCount, bool endOfReque _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 @@ internal HttpApiTypes.HTTP_FLAGS ComputeHeaders(long writeCount, bool endOfReque 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 @@ private static void FreePinnedHeaders(List pinnedHeaders) } } + 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 8f6341db63ce..6b181cc312cc 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs @@ -6,6 +6,7 @@ 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 @@ public override void Flush() 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 @@ public override void Flush() 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 @@ public override void Flush() 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 @@ private List PinDataBuffers(bool endOfRequest, ArraySegment data chunkCount += 1; } } + dataChunks = new HttpApiTypes.HTTP_DATA_CHUNK[chunkCount]; if (chunked) @@ -224,7 +237,10 @@ private List PinDataBuffers(bool endOfRequest, ArraySegment data 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 @@ private List PinDataBuffers(bool endOfRequest, ArraySegment data } } + if (addTrailers) + { + _requestContext.Response.SerializeTrailers(dataChunks, currentChunk, pins); + } + else if (endOfRequest) + { + _requestContext.Response.MakeTrailersReadOnly(); + } + return pins; } @@ -320,7 +345,7 @@ private unsafe Task FlushInternalAsync(ArraySegment data, CancellationToke } catch (Exception e) { - LogHelper.LogException(Logger, "FlushAsync", e); + Logger.LogError(LoggerEventIds.ErrorWhenFlushAsync, e, "FlushAsync"); asyncResult.Dispose(); Abort(); throw; @@ -330,21 +355,21 @@ private unsafe Task FlushInternalAsync(ArraySegment data, CancellationToke { 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 @@ private HttpApiTypes.HTTP_FLAGS ComputeLeftToWrite(long writeCount, bool endOfRe { 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 @@ private HttpApiTypes.HTTP_FLAGS ComputeLeftToWrite(long writeCount, bool endOfRe // 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 @@ internal unsafe Task SendFileAsyncCore(string fileName, long offset, long? count } catch (Exception e) { - LogHelper.LogException(Logger, "SendFileAsync", e); + Logger.LogError(LoggerEventIds.FileSendAsyncError, e, "SendFileAsync"); asyncResult.Dispose(); Abort(); throw; @@ -622,21 +649,21 @@ internal unsafe Task SendFileAsyncCore(string fileName, long offset, long? count { 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 @@ protected override unsafe void Dispose(bool disposing) { 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 32a90e4b51bb..c3248c3b7267 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/ResponseStreamAsyncResult.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/ResponseStreamAsyncResult.cs @@ -8,6 +8,7 @@ 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 @@ private static void IOCompleted(ResponseStreamAsyncResult asyncResult, uint erro { 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 @@ private static void IOCompleted(ResponseStreamAsyncResult asyncResult, uint erro // 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 @@ private static void IOCompleted(ResponseStreamAsyncResult asyncResult, uint erro } 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 67b954a93432..d51faf9fd4d8 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 e63155c8232a..304b39b07003 100644 --- a/src/Servers/HttpSys/src/StandardFeatureCollection.cs +++ b/src/Servers/HttpSys/src/StandardFeatureCollection.cs @@ -27,6 +27,8 @@ internal sealed class StandardFeatureCollection : IFeatureCollection { 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 e38dd9a5d2d8..ac92c6591df5 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 @@ public class UrlPrefixCollection : ICollection 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 @@ internal void RegisterAllPrefixes(UrlGroup urlGroup) { _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 7b4caaef624b..f7c2b9445f5d 100644 --- a/src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs +++ b/src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs @@ -9,16 +9,19 @@ 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 @@ public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder) } /// - /// 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 000000000000..94b00042d27c --- /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 be66db96558b..2886b2d8d148 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/AuthenticationTests.cs @@ -368,6 +368,38 @@ public async Task AuthTypes_UnathorizedAuthenticatedAuthType_Unauthorized(Authen } } + [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 000000000000..57e5722557d6 --- /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 6bbaeb13cb95..43d6005bc181 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 @@ 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 @@ public async Task Https_200OK_Success() } [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 @@ public async Task Https_SendHelloWorld_Success() } [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 @@ public async Task Https_EchoHelloWorld_Success() } [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 @@ public async Task Https_ClientCertRequested_ClientCertPresent() } [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 @@ public async Task Https_SkipsITlsHandshakeFeatureOnWin7() } [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 @@ public async Task Https_SetsITlsHandshakeFeature() } [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 @@ public async Task Https_ITlsHandshakeFeature_MatchesIHttpSysExtensionInfoFeature 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 6a7d29d654ed..8e2860cf69cb 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 @@ public async Task RequestBody_ReadAsyncPartialBodyAndCancel_Canceled() } [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 250ff9c9be96..0e5270b82b7d 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs @@ -77,7 +77,7 @@ public void DoesNotOverrideDirectConfigurationWithIServerAddressesFeature_IfAddr [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 @@ public void OverridingIServerAdressesFeatureWithDirectConfiguration_WarnsOnStart } [ConditionalFact] - public void UseIServerAdressesFeature_WhenNoDirectConfiguration() + public void UseIServerAddressesFeature_WhenNoDirectConfiguration() { var serverAddress = "http://localhost:11001/"; @@ -114,7 +114,8 @@ public void UseDefaultAddress_WhenNoServerAddressAndNoDirectConfiguration() { 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 281d455f5e65..d0a2c7558b65 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 ee257df98950..000000000000 --- 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 b722f3ab1faa..a2c39a4c47da 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/OpaqueUpgradeTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/OpaqueUpgradeTests.cs @@ -6,7 +6,6 @@ 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 @@ public async Task OpaqueUpgrade_DownLevel_FeatureIsAbsent() } [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 @@ public async Task OpaqueUpgrade_SupportKeys_Present() } [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 @@ public async Task OpaqueUpgrade_AfterHeadersSent_Throws() } [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 @@ public async Task OpaqueUpgrade_GetUpgrade_Success() } [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 @@ public async Task OpaqueUpgrade_GetUpgrade_NotAffectedByMaxRequestBodyLimit() } [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 @@ public async Task OpaqueUpgrade_WithOnStarting_CallbackCalled() } [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 @@ public async Task OpaqueUpgrade_VariousMethodsUpgradeSendAndReceive_Success(stri } [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 4a45a81a5d20..bd73e399467b 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ResponseBodyTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ResponseBodyTests.cs @@ -30,6 +30,7 @@ public async Task ResponseBody_StartAsync_LocksHeadersAndTriggersOnStarting() 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 @@ public async Task ResponseBody_CompleteAsync_TriggersOnStartingAndLocksHeaders() 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 bda37a76c53b..1112420ed662 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs @@ -45,7 +45,7 @@ public async Task Caching_NoCacheControl_NotCached() } [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 @@ public async Task Caching_JustPublic_NotCached() } [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 @@ public async Task Caching_WithoutContentType_NotCached() } [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 @@ public async Task Caching_DisallowedResponseHeaders_NotCached(string headerName) [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 @@ public async Task Caching_SendFileWithFullContentLength_Cached() } [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 000000000000..75f43587b73d --- /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 04228c6da6e4..fbea889de4aa 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs @@ -131,7 +131,7 @@ public async Task Server_EchoHelloWorld_Success() } [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 8ba9e7b39a7f..a347ce427f87 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 @@ internal static class Utilities { // 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 @@ internal static IWebHost CreateDynamicHost(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 @@ internal static MessagePump CreatePump(Action configureOptions) 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 8614ac36db6b..20d0f0713f9f 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 bb4202398864..b7c7fbf97241 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 208d4a1eaacc..353b0be78866 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 0aade370611f..792f053a53b7 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 ae02dd3faad0..8c9cece3e303 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 45e3699fadc3..01b6ecfa7bce 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 05fd514bea5e..517f00d15eb5 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 @@ class ConfigUtility 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 091a99e221ee..9f24c25f67b7 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 000000000000..88a3d89dd6ea --- /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 000000000000..18f1759669ac --- /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 000000000000..e96043e8c4f8 --- /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 000000000000..b7b334059aaf --- /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 9319e5643d34..cde38374fe67 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 07828830d704..7b92e58daf1c 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/percpu.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/percpu.h @@ -82,7 +82,7 @@ class PER_CPU ); // - // 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 29da773bcab8..5b1d0adbee86 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringa.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringa.cpp @@ -1637,7 +1637,7 @@ Routine Description: 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 @@ Routine Description: 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 @@ Routine Description: 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 74f8595482b4..c83096b97a47 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringu.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/IISLib/stringu.cpp @@ -1065,7 +1065,7 @@ Routine Description: 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 @@ Routine Description: 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 @@ Routine Description: 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 baa50726ce61..087e8abf7470 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 01ec10f6f6b5..c2ff5e0a7d59 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 fe7f9d10a0ea..29d4ad614ffe 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 be2e9055b777..4f86cde3b852 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 4fbf2abf4074..82737cf24bfd 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 cc855dfcf26a..c564a7b4f03a 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h @@ -179,6 +179,7 @@ class FORWARDING_HANDLER : public REQUEST_HANDLER 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 82541f1bdf4f..248ca1aa9354 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 2842dc02454f..3a6fef93353b 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h @@ -78,9 +78,27 @@ class ENVIRONMENT_VAR_HELPERS 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 d3a705e9a2b3..c32bc51ced79 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 5f06c1269587..1552e8d075b6 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.h @@ -224,6 +224,12 @@ class REQUESTHANDLER_CONFIG return !m_fEnableOutOfProcessConsoleRedirection.Equals(L"false", 1); } + STRU* + QueryForwardResponseConnectionHeader() + { + return &m_struForwardResponseConnectionHeader; + } + protected: // @@ -255,6 +261,7 @@ class REQUESTHANDLER_CONFIG 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 727746871fce..c048b28f8dcc 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 @@ public FirstRequestConfig() 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 9435906632b3..2abb302273f5 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 3cb8d3a1c80a..dd561126d632 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 @@ public class StartupTimeBenchmark 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 d83e4e0301fd..a470dc92b4ba 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 635097115dd5..479bfe8e6c03 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 8ff01c778fa1..73dbd4cbb98b 100644 --- a/src/Servers/IIS/IIS/src/Core/DuplexStream.cs +++ b/src/Servers/IIS/IIS/src/Core/DuplexStream.cs @@ -60,9 +60,29 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel 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 9fa5d7405e44..8d90fd69a59c 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 @@ public override int Read(byte[] buffer, int offset, int count) 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 @@ private async ValueTask ReadAsyncInternal(Memory buffer, Cancellation } } + 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 739b6b16f5cf..b1d0c1f22af5 100644 --- a/src/Servers/IIS/IIS/src/Core/HttpResponseStream.cs +++ b/src/Servers/IIS/IIS/src/Core/HttpResponseStream.cs @@ -24,7 +24,7 @@ public HttpResponseStream(IHttpBodyControlFeature bodyControl, IISHttpContext co public override void Flush() { - FlushAsync(default(CancellationToken)).GetAwaiter().GetResult(); + FlushAsync(default).GetAwaiter().GetResult(); } public override Task FlushAsync(CancellationToken cancellationToken) @@ -41,45 +41,17 @@ public override void Write(byte[] buffer, int offset, int count) 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 cab956dd0a88..1cdfc4be3285 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 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 @@ internal partial class IISHttpContext : IFeatureCollection, // 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 @@ private void ExtraFeatureSet(Type key, object value) 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 68141058c5c8..5f831eb01a3e 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 @@ internal async ValueTask ReadAsync(Memory memory, CancellationToken c } } + 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 cae357e77612..74f0cbae6b4b 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs @@ -409,7 +409,7 @@ public unsafe void SetResponseHeaders() } } - public abstract Task ProcessRequestAsync(); + public abstract Task ProcessRequestAsync(); public void OnStarting(Func callback, object state) { @@ -599,10 +599,9 @@ public void Execute() private async Task HandleRequest() { - bool successfulRequest = false; try { - successfulRequest = await ProcessRequestAsync(); + await ProcessRequestAsync(); } catch (Exception ex) { @@ -610,19 +609,9 @@ private async Task HandleRequest() } 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 372eadfa71e4..060f105233cc 100644 --- a/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs +++ b/src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs @@ -21,32 +21,33 @@ public IISHttpContextOfT(MemoryPool memoryPool, IHttpApplication _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 @@ public override async Task ProcessRequestAsync() // 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 @@ public override async Task ProcessRequestAsync() 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 2dee24f7b971..7d295c6614aa 100644 --- a/src/Servers/IIS/IIS/src/Core/OutputProducer.cs +++ b/src/Servers/IIS/IIS/src/Core/OutputProducer.cs @@ -20,7 +20,7 @@ internal class OutputProducer 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 c645b127412c..a5998c3fa01b 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 5f8f3edf5a53..10fe17b4d2d3 100644 --- a/src/Servers/IIS/IIS/src/StartupHook.cs +++ b/src/Servers/IIS/IIS/src/StartupHook.cs @@ -81,6 +81,7 @@ public static void Initialize() 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 4bd29675ae1c..89c44e719f4b 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 @@ public async Task AppOfflineDroppedWhileSiteFailedToStartInShim_AppOfflineServed 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 @@ public async Task AppOfflineDropped_CanRemoveAppOfflineAfterAddingAndSiteWorks(H [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 6130cdf8828c..36520369a695 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 @@ public BasicAuthTests(PublishedSitesFixture fixture) : base(fixture) 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 ccc4ea645884..b0847f84a1b3 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 @@ public ClientCertificateTests(PublishedSitesFixture fixture, ClientCertificateFi 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 @@ public Task HttpsNoClientCert_NoClientCert(TestVariant variant) [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 @@ private async Task ClientCertTest(TestVariant variant, bool sendClientCert) 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 82df45fdbfc4..38f4dae818c2 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 @@ public CommonStartupTests(PublishedSitesFixture fixture) : base(fixture) 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 386b749747ca..66ffde7f2086 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 @@ public async Task OutOfProcessToInProcessHostingModelSwitchWorks() [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 c93faacdc002..5b2323223a6a 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 @@ public HttpsTests(PublishedSitesFixture fixture) : base(fixture) 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 @@ public async Task ServerAddressesIncludesBaseAddress() 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 @@ public async Task ServerAddressesIncludesBaseAddress() 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 @@ public async Task MultipleHttpsPortsProduceNoEnvVar() 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 7f8d003eb791..2a8140aec5b1 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 @@ private async Task WebConfigExpandsVariables(HostingModel hostingModel) 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 a1d540a8b3b4..5104520781f1 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 @@ public async Task IncludesAdditionalErrorPageTextInProcessHandlerLoadFailure_Cor 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 @@ public async Task IncludesAdditionalErrorPageTextOutOfProcessHandlerLoadFailure_ 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 @@ public async Task IncludesAdditionalErrorPageTextInProcessStartupFailure_Correct 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 2e2866e98e08..7573fc021d5b 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 @@ public async Task CheckFailedRequestEvents() // 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 d45024441ed3..d1867c8044e5 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 @@ public async Task ResponseBodyTest_FlushedPipeAndThenUnflushedPipe_AutoFlushed() { 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 cb7fc11d298e..4dcd67f63c5c 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 @@ public async Task Gets500_30_ErrorPage() 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 1c070242e038..c030a87161e2 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 @@ public async Task InvalidProcessPath_ExpectServerError(string path, string argum 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 @@ public async Task StartsWithDotnetOnThePath(string path) [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 @@ public async Task DoesNotStartIfDisabled() public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAncmV2InProcess(); @@ -271,7 +277,7 @@ public async Task RemoveHostfxrFromApp_InProcessHostfxrAPIAbsent() if (DeployerSelector.HasNewShim) { - await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "HTTP Error 500.32 - ANCM Failed to Load dll"); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.32"); } else { @@ -314,11 +320,11 @@ public async Task PublishWithWrongBitness() 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 +341,7 @@ public async Task RemoveHostfxrFromApp_InProcessHostfxrLoadFailure() if (DeployerSelector.HasNewShim) { - await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "HTTP Error 500.32 - ANCM Failed to Load dll"); + await AssertSiteFailsToStartWithInProcessStaticContent(deploymentResult, "500.32"); } else { @@ -354,7 +360,7 @@ public async Task TargedDifferenceSharedFramework_FailedToFindNativeDependencies 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 +380,7 @@ public async Task SingleExecutable_FailedToFindNativeDependencies() 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 +402,7 @@ public async Task TargedDifferenceSharedFramework_FailedToFindNativeDependencies 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 +422,14 @@ public async Task RemoveInProcessReference_FailedToFindRequestHandler() 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 +468,7 @@ public async Task StartupTimeoutIsApplied() 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); } } } @@ -765,7 +771,6 @@ public async Task ExceptionIsLoggedToEventLogAndPutInResponseDuringHostingStartu Assert.Contains("InvalidOperationException", content); Assert.Contains("TestSite.Program.Main", content); Assert.Contains("From Configure", content); - Assert.DoesNotContain("ANCM In-Process Start Failure", content); StopServer(); @@ -875,6 +880,39 @@ public async Task StackOverflowCanBeSetBySettingLargerStackViaHandlerSetting() 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 +970,7 @@ private static void MoveApplication( 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 034cc48fd721..e896acc67c35 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 @@ public SynchronousReadAndWriteTests(IISTestSiteFixture fixture): base(fixture) } [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 70f6a6b8b464..0a6018731500 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 @@ public LoggingTests(PublishedSitesFixture fixture) : base(fixture) public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels(); @@ -171,7 +171,7 @@ public async Task DebugLogsAreWrittenToEventLog(TestVariant variant) [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 eb94b683f13d..15a935410867 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 @@ public async Task FailsAndLogsWhenRunningTwoInProcessApps() 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 @@ public async Task FailsAndLogsEventLogForMixedHostingModel(HostingModel firstApp 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 8557b89f94b4..969fc2c05b10 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 @@ public AspNetCorePortTests(PublishedSitesFixture fixture) : base(fixture) 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 7837d47dce07..e94c4503fb2a 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 @@ public async Task GlobalVersion_NewVersionNumber_Fails(string version) 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 deaf45f95a90..205401142211 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 @@ public HelloWorldTests(PublishedSitesFixture fixture) : base(fixture) 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 8bf370e640b6..bbcb5109456a 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 @@ public override Task Publish(DeploymentParameters deployme 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 a0fd00f86997..70a7606e318f 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 @@ public IISDeploymentParameters GetBaseDeploymentParameters(ApplicationPublisher 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 e15863c0fb7f..1817d27fd5b9 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.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 645e595cbf95..5c3829c09eb5 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.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 @@ private void EnsureInitialized() { 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 f9716c2a2634..e22f96dadc0a 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 @@ public WindowsAuthTests(PublishedSitesFixture fixture) : base(fixture) 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 a7858c63b239..5d689a6bee06 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 9a74ecd31d20..530f6f8bbe19 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 03adce1d0920..07b99fa8d560 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 fad488d4841c..d6fc23a8035c 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 @@ static RequiresIISAttribute() 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 b662fd473232..354b8a2229ce 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 @@ 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 @@ public async Task ReaderThrowsCanceledException() } [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 8efb9601a39a..7847fffccbbd 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 @@ 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 981a3e6d45c6..1700351f6b3b 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 @@ 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 dda87b811a8d..1b54904dee94 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 8bac31d82ccd..1f46ed1b4b48 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 @@ 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 4b59d3129854..c240382137ea 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 @@ 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 e1564b8cfe5f..a7a657354619 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 e350d679c2e0..55a4ad69957e 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 @@ 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 23a5ecdf7159..d874d0093e05 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 1ebc0981bd59..4e75d204c805 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 @@ 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 17f557a3387f..c63c777c8068 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 @@ public NtlmAuthenticationTests(PublishedSitesFixture fixture) : base(fixture) 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 7336dda665d5..95ae863ffd25 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 @@ private async Task ServerAddresses(HttpContext ctx) 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 @@ public Task CreateFile(HttpContext context) 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 @@ public async Task HTTPS_PORT(HttpContext context) 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 9d45cf24cffc..d8f4fce66917 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 acfcaecc42da..a25c4a0d5ead 100644 --- a/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs +++ b/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs @@ -334,7 +334,7 @@ private void ConfigureAppHostConfig(XElement config, string contentRoot, int por 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 90f76fd8c29f..89b50d5450cd 100644 --- a/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeploymentParameterExtensions.cs +++ b/src/Servers/IIS/IntegrationTesting.IIS/src/IISDeploymentParameterExtensions.cs @@ -27,6 +27,21 @@ public static void AddServerConfigAction(this IISDeploymentParameters parameters } 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 066f50aca85e..4ff733d3871d 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 4dec82338a3a..000000000000 --- 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 ff3785034676..9f8403c50ecf 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 1bb12c2437d1..6673afaa1cc1 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 @@ public Http2Limits() { } 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 @@ public enum HttpProtocols 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 @@ public void Dispose() { } 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 @@ public KestrelServerLimits() { } 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 @@ public void ListenLocalhost(int port, 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 @@ public enum HttpMethod : byte 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 @@ public enum HttpVersion 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 @@ public enum ClientCertificateMode 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 929a4087786f..16f7ab0fce5b 100644 --- a/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs +++ b/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs @@ -139,6 +139,9 @@ internal static BadHttpRequestException GetException(RequestRejectionReason reas 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 59bd610f1e95..f84ed1d2cef1 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 000000000000..fa82ede8c3bd --- /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 09524bf156cc..294ae6ae69ad 100644 --- a/src/Servers/Kestrel/Core/src/HttpProtocols.cs +++ b/src/Servers/Kestrel/Core/src/HttpProtocols.cs @@ -12,5 +12,7 @@ public enum HttpProtocols 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 df08a8d320d9..a373c240bf8d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -53,7 +53,7 @@ async Task AcceptConnectionsAsync() // 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 f94d4220629b..000000000000 --- 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 75186e8ad60d..6decc434a238 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 @@ public static int BeginChunkBytes(int dataCount, Span span) 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 @@ internal static int GetPrefixBytesForChunk(int length, out bool sliceOneByte) // 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 371033b3302e..85b9ad8b6163 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 @@ public void OnHeartbeat(DateTimeOffset now) 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 374ab1144f2a..9c87b3697e39 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 @@ private void Copy(in ReadOnlySequence readableBuffer, PipeWriter writableB { 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 d703bc71fe42..832d48cdce4c 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 @@ internal partial class Http1Connection : HttpProtocol, IRequestProcessor 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 4b5d5b087259..c5993cd7ccfc 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 @@ internal abstract class Http1MessageBody : MessageBody 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 b4c67c13a3e8..322e46190dd0 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 @@ public Http1ParsingHandler(Http1Connection connection, bool trailers) Trailers = trailers; } - public void OnHeader(Span name, Span value) + public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) { if (Trailers) { @@ -34,7 +35,7 @@ public void OnHeader(Span name, Span value) } } - public void OnHeadersComplete() + public void OnHeadersComplete(bool endStream) { if (Trailers) { @@ -48,5 +49,15 @@ public void OnHeadersComplete() 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 aa22d87a8631..516d7c32ecca 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 @@ 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,20 +422,88 @@ public StringValues HeaderLastModified _headers._LastModified = value; } } - public StringValues HeaderAccept + public StringValues HeaderAuthority { get { StringValues value = default; if ((_bits & 0x80000L) != 0) { - value = _headers._Accept; + 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; + } + return value; + } + set + { + _bits |= 0x800000L; _headers._Accept = value; } } @@ -369,7 +512,7 @@ public StringValues HeaderAcceptCharset get { StringValues value = default; - if ((_bits & 0x100000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._AcceptCharset; } @@ -377,7 +520,7 @@ public StringValues HeaderAcceptCharset } set { - _bits |= 0x100000L; + _bits |= 0x1000000L; _headers._AcceptCharset = value; } } @@ -386,7 +529,7 @@ public StringValues HeaderAcceptEncoding get { StringValues value = default; - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._AcceptEncoding; } @@ -394,7 +537,7 @@ public StringValues HeaderAcceptEncoding } set { - _bits |= 0x200000L; + _bits |= 0x2000000L; _headers._AcceptEncoding = value; } } @@ -403,7 +546,7 @@ public StringValues HeaderAcceptLanguage get { StringValues value = default; - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._AcceptLanguage; } @@ -411,7 +554,7 @@ public StringValues HeaderAcceptLanguage } set { - _bits |= 0x400000L; + _bits |= 0x4000000L; _headers._AcceptLanguage = value; } } @@ -420,7 +563,7 @@ public StringValues HeaderAuthorization get { StringValues value = default; - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._Authorization; } @@ -428,7 +571,7 @@ public StringValues HeaderAuthorization } set { - _bits |= 0x800000L; + _bits |= 0x8000000L; _headers._Authorization = value; } } @@ -437,7 +580,7 @@ public StringValues HeaderCookie get { StringValues value = default; - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Cookie; } @@ -445,7 +588,7 @@ public StringValues HeaderCookie } set { - _bits |= 0x1000000L; + _bits |= 0x10000000L; _headers._Cookie = value; } } @@ -454,7 +597,7 @@ public StringValues HeaderExpect get { StringValues value = default; - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._Expect; } @@ -462,7 +605,7 @@ public StringValues HeaderExpect } set { - _bits |= 0x2000000L; + _bits |= 0x20000000L; _headers._Expect = value; } } @@ -471,7 +614,7 @@ public StringValues HeaderFrom get { StringValues value = default; - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._From; } @@ -479,7 +622,7 @@ public StringValues HeaderFrom } set { - _bits |= 0x4000000L; + _bits |= 0x40000000L; _headers._From = value; } } @@ -488,7 +631,7 @@ public StringValues HeaderHost get { StringValues value = default; - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._Host; } @@ -496,7 +639,7 @@ public StringValues HeaderHost } set { - _bits |= 0x8000000L; + _bits |= 0x80000000L; _headers._Host = value; } } @@ -505,7 +648,7 @@ public StringValues HeaderIfMatch get { StringValues value = default; - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._IfMatch; } @@ -513,7 +656,7 @@ public StringValues HeaderIfMatch } set { - _bits |= 0x10000000L; + _bits |= 0x100000000L; _headers._IfMatch = value; } } @@ -522,7 +665,7 @@ public StringValues HeaderIfModifiedSince get { StringValues value = default; - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._IfModifiedSince; } @@ -530,7 +673,7 @@ public StringValues HeaderIfModifiedSince } set { - _bits |= 0x20000000L; + _bits |= 0x200000000L; _headers._IfModifiedSince = value; } } @@ -539,7 +682,7 @@ public StringValues HeaderIfNoneMatch get { StringValues value = default; - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._IfNoneMatch; } @@ -547,7 +690,7 @@ public StringValues HeaderIfNoneMatch } set { - _bits |= 0x40000000L; + _bits |= 0x400000000L; _headers._IfNoneMatch = value; } } @@ -556,7 +699,7 @@ public StringValues HeaderIfRange get { StringValues value = default; - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._IfRange; } @@ -564,7 +707,7 @@ public StringValues HeaderIfRange } set { - _bits |= 0x80000000L; + _bits |= 0x800000000L; _headers._IfRange = value; } } @@ -573,7 +716,7 @@ public StringValues HeaderIfUnmodifiedSince get { StringValues value = default; - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x1000000000L) != 0) { value = _headers._IfUnmodifiedSince; } @@ -581,7 +724,7 @@ public StringValues HeaderIfUnmodifiedSince } set { - _bits |= 0x100000000L; + _bits |= 0x1000000000L; _headers._IfUnmodifiedSince = value; } } @@ -590,7 +733,7 @@ public StringValues HeaderMaxForwards get { StringValues value = default; - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x2000000000L) != 0) { value = _headers._MaxForwards; } @@ -598,7 +741,7 @@ public StringValues HeaderMaxForwards } set { - _bits |= 0x200000000L; + _bits |= 0x2000000000L; _headers._MaxForwards = value; } } @@ -607,7 +750,7 @@ public StringValues HeaderProxyAuthorization get { StringValues value = default; - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x4000000000L) != 0) { value = _headers._ProxyAuthorization; } @@ -615,7 +758,7 @@ public StringValues HeaderProxyAuthorization } set { - _bits |= 0x400000000L; + _bits |= 0x4000000000L; _headers._ProxyAuthorization = value; } } @@ -624,7 +767,7 @@ public StringValues HeaderReferer get { StringValues value = default; - if ((_bits & 0x800000000L) != 0) + if ((_bits & 0x8000000000L) != 0) { value = _headers._Referer; } @@ -632,7 +775,7 @@ public StringValues HeaderReferer } set { - _bits |= 0x800000000L; + _bits |= 0x8000000000L; _headers._Referer = value; } } @@ -641,7 +784,7 @@ public StringValues HeaderRange get { StringValues value = default; - if ((_bits & 0x1000000000L) != 0) + if ((_bits & 0x10000000000L) != 0) { value = _headers._Range; } @@ -649,7 +792,7 @@ public StringValues HeaderRange } set { - _bits |= 0x1000000000L; + _bits |= 0x10000000000L; _headers._Range = value; } } @@ -658,7 +801,7 @@ public StringValues HeaderTE get { StringValues value = default; - if ((_bits & 0x2000000000L) != 0) + if ((_bits & 0x20000000000L) != 0) { value = _headers._TE; } @@ -666,7 +809,7 @@ public StringValues HeaderTE } set { - _bits |= 0x2000000000L; + _bits |= 0x20000000000L; _headers._TE = value; } } @@ -675,7 +818,7 @@ public StringValues HeaderTranslate get { StringValues value = default; - if ((_bits & 0x4000000000L) != 0) + if ((_bits & 0x40000000000L) != 0) { value = _headers._Translate; } @@ -683,7 +826,7 @@ public StringValues HeaderTranslate } set { - _bits |= 0x4000000000L; + _bits |= 0x40000000000L; _headers._Translate = value; } } @@ -692,7 +835,7 @@ public StringValues HeaderUserAgent get { StringValues value = default; - if ((_bits & 0x8000000000L) != 0) + if ((_bits & 0x80000000000L) != 0) { value = _headers._UserAgent; } @@ -700,7 +843,7 @@ public StringValues HeaderUserAgent } set { - _bits |= 0x8000000000L; + _bits |= 0x80000000000L; _headers._UserAgent = value; } } @@ -709,7 +852,7 @@ public StringValues HeaderDNT get { StringValues value = default; - if ((_bits & 0x10000000000L) != 0) + if ((_bits & 0x100000000000L) != 0) { value = _headers._DNT; } @@ -717,7 +860,7 @@ public StringValues HeaderDNT } set { - _bits |= 0x10000000000L; + _bits |= 0x100000000000L; _headers._DNT = value; } } @@ -726,7 +869,7 @@ public StringValues HeaderUpgradeInsecureRequests get { StringValues value = default; - if ((_bits & 0x20000000000L) != 0) + if ((_bits & 0x200000000000L) != 0) { value = _headers._UpgradeInsecureRequests; } @@ -734,7 +877,7 @@ public StringValues HeaderUpgradeInsecureRequests } set { - _bits |= 0x20000000000L; + _bits |= 0x200000000000L; _headers._UpgradeInsecureRequests = value; } } @@ -743,7 +886,7 @@ public StringValues HeaderRequestId get { StringValues value = default; - if ((_bits & 0x40000000000L) != 0) + if ((_bits & 0x400000000000L) != 0) { value = _headers._RequestId; } @@ -751,7 +894,7 @@ public StringValues HeaderRequestId } set { - _bits |= 0x40000000000L; + _bits |= 0x400000000000L; _headers._RequestId = value; } } @@ -760,7 +903,7 @@ public StringValues HeaderCorrelationContext get { StringValues value = default; - if ((_bits & 0x80000000000L) != 0) + if ((_bits & 0x800000000000L) != 0) { value = _headers._CorrelationContext; } @@ -768,7 +911,7 @@ public StringValues HeaderCorrelationContext } set { - _bits |= 0x80000000000L; + _bits |= 0x800000000000L; _headers._CorrelationContext = value; } } @@ -777,7 +920,7 @@ public StringValues HeaderTraceParent get { StringValues value = default; - if ((_bits & 0x100000000000L) != 0) + if ((_bits & 0x1000000000000L) != 0) { value = _headers._TraceParent; } @@ -785,7 +928,7 @@ public StringValues HeaderTraceParent } set { - _bits |= 0x100000000000L; + _bits |= 0x1000000000000L; _headers._TraceParent = value; } } @@ -794,7 +937,7 @@ public StringValues HeaderTraceState get { StringValues value = default; - if ((_bits & 0x200000000000L) != 0) + if ((_bits & 0x2000000000000L) != 0) { value = _headers._TraceState; } @@ -802,7 +945,7 @@ public StringValues HeaderTraceState } set { - _bits |= 0x200000000000L; + _bits |= 0x2000000000000L; _headers._TraceState = value; } } @@ -811,7 +954,7 @@ public StringValues HeaderOrigin get { StringValues value = default; - if ((_bits & 0x400000000000L) != 0) + if ((_bits & 0x4000000000000L) != 0) { value = _headers._Origin; } @@ -819,7 +962,7 @@ public StringValues HeaderOrigin } set { - _bits |= 0x400000000000L; + _bits |= 0x4000000000000L; _headers._Origin = value; } } @@ -828,7 +971,7 @@ public StringValues HeaderAccessControlRequestMethod get { StringValues value = default; - if ((_bits & 0x800000000000L) != 0) + if ((_bits & 0x8000000000000L) != 0) { value = _headers._AccessControlRequestMethod; } @@ -836,7 +979,7 @@ public StringValues HeaderAccessControlRequestMethod } set { - _bits |= 0x800000000000L; + _bits |= 0x8000000000000L; _headers._AccessControlRequestMethod = value; } } @@ -845,7 +988,7 @@ public StringValues HeaderAccessControlRequestHeaders get { StringValues value = default; - if ((_bits & 0x1000000000000L) != 0) + if ((_bits & 0x10000000000000L) != 0) { value = _headers._AccessControlRequestHeaders; } @@ -853,7 +996,7 @@ public StringValues HeaderAccessControlRequestHeaders } set { - _bits |= 0x1000000000000L; + _bits |= 0x10000000000000L; _headers._AccessControlRequestHeaders = value; } } @@ -888,7 +1031,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.TE, key)) { - if ((_bits & 0x2000000000L) != 0) + if ((_bits & 0x20000000000L) != 0) { value = _headers._TE; return true; @@ -898,7 +1041,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.TE.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000000L) != 0) + if ((_bits & 0x20000000000L) != 0) { value = _headers._TE; return true; @@ -920,7 +1063,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.DNT, key)) { - if ((_bits & 0x10000000000L) != 0) + if ((_bits & 0x100000000000L) != 0) { value = _headers._DNT; return true; @@ -939,7 +1082,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.DNT.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000000L) != 0) + if ((_bits & 0x100000000000L) != 0) { value = _headers._DNT; return true; @@ -952,7 +1095,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.Host, key)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._Host; return true; @@ -970,7 +1113,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.From, key)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._From; return true; @@ -980,7 +1123,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.Host.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._Host; return true; @@ -998,7 +1141,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.From.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._From; return true; @@ -1009,6 +1152,15 @@ protected override bool TryGetValueFast(string key, out StringValues value) } 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 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.Range, key)) { - if ((_bits & 0x1000000000L) != 0) + if ((_bits & 0x10000000000L) != 0) { value = _headers._Range; return true; @@ -1028,6 +1180,15 @@ protected override bool TryGetValueFast(string key, out StringValues value) 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 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.Range.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000000L) != 0) + if ((_bits & 0x10000000000L) != 0) { value = _headers._Range; return true; @@ -1052,7 +1213,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.Accept, key)) { - if ((_bits & 0x80000L) != 0) + if ((_bits & 0x800000L) != 0) { value = _headers._Accept; return true; @@ -1070,7 +1231,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.Cookie, key)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Cookie; return true; @@ -1079,7 +1240,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.Expect, key)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._Expect; return true; @@ -1088,7 +1249,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.Origin, key)) { - if ((_bits & 0x400000000000L) != 0) + if ((_bits & 0x4000000000000L) != 0) { value = _headers._Origin; return true; @@ -1098,7 +1259,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.Accept.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000L) != 0) + if ((_bits & 0x800000L) != 0) { value = _headers._Accept; return true; @@ -1116,7 +1277,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.Cookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Cookie; return true; @@ -1125,7 +1286,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.Expect.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._Expect; return true; @@ -1134,7 +1295,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.Origin.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000000L) != 0) + if ((_bits & 0x4000000000000L) != 0) { value = _headers._Origin; return true; @@ -1145,6 +1306,24 @@ protected override bool TryGetValueFast(string key, out StringValues value) } 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 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.Referer, key)) { - if ((_bits & 0x800000000L) != 0) + if ((_bits & 0x8000000000L) != 0) { value = _headers._Referer; return true; @@ -1191,6 +1370,24 @@ protected override bool TryGetValueFast(string key, out StringValues value) 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 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.Referer.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000000L) != 0) + if ((_bits & 0x8000000000L) != 0) { value = _headers._Referer; return true; @@ -1242,7 +1439,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.IfMatch, key)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._IfMatch; return true; @@ -1251,7 +1448,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.IfRange, key)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._IfRange; return true; @@ -1261,7 +1458,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.IfMatch.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._IfMatch; return true; @@ -1270,7 +1467,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.IfRange.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._IfRange; return true; @@ -1283,7 +1480,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.Translate, key)) { - if ((_bits & 0x4000000000L) != 0) + if ((_bits & 0x40000000000L) != 0) { value = _headers._Translate; return true; @@ -1293,7 +1490,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.Translate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000000L) != 0) + if ((_bits & 0x40000000000L) != 0) { value = _headers._Translate; return true; @@ -1313,9 +1510,18 @@ protected override bool TryGetValueFast(string key, out StringValues value) } 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 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.RequestId, key)) { - if ((_bits & 0x40000000000L) != 0) + if ((_bits & 0x400000000000L) != 0) { value = _headers._RequestId; return true; @@ -1342,7 +1548,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.TraceState, key)) { - if ((_bits & 0x200000000000L) != 0) + if ((_bits & 0x2000000000000L) != 0) { value = _headers._TraceState; return true; @@ -1359,9 +1565,18 @@ protected override bool TryGetValueFast(string key, out StringValues value) } 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 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.RequestId.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000000L) != 0) + if ((_bits & 0x400000000000L) != 0) { value = _headers._RequestId; return true; @@ -1388,7 +1603,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.TraceState.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000000L) != 0) + if ((_bits & 0x2000000000000L) != 0) { value = _headers._TraceState; return true; @@ -1410,7 +1625,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.TraceParent, key)) { - if ((_bits & 0x100000000000L) != 0) + if ((_bits & 0x1000000000000L) != 0) { value = _headers._TraceParent; return true; @@ -1429,7 +1644,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.TraceParent.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000000L) != 0) + if ((_bits & 0x1000000000000L) != 0) { value = _headers._TraceParent; return true; @@ -1451,7 +1666,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.MaxForwards, key)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x2000000000L) != 0) { value = _headers._MaxForwards; return true; @@ -1470,7 +1685,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.MaxForwards.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x2000000000L) != 0) { value = _headers._MaxForwards; return true; @@ -1510,7 +1725,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.Authorization, key)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._Authorization; return true; @@ -1519,7 +1734,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.IfNoneMatch, key)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._IfNoneMatch; return true; @@ -1556,7 +1771,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.Authorization.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._Authorization; return true; @@ -1565,7 +1780,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.IfNoneMatch.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._IfNoneMatch; return true; @@ -1578,7 +1793,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.AcceptCharset, key)) { - if ((_bits & 0x100000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._AcceptCharset; return true; @@ -1597,7 +1812,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.AcceptCharset.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._AcceptCharset; return true; @@ -1619,7 +1834,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.AcceptEncoding, key)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._AcceptEncoding; return true; @@ -1628,7 +1843,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.AcceptLanguage, key)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._AcceptLanguage; return true; @@ -1638,7 +1853,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.AcceptEncoding.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._AcceptEncoding; return true; @@ -1647,7 +1862,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.AcceptLanguage.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._AcceptLanguage; return true; @@ -1728,7 +1943,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.IfModifiedSince, key)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._IfModifiedSince; return true; @@ -1747,7 +1962,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.IfModifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._IfModifiedSince; return true; @@ -1760,7 +1975,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.IfUnmodifiedSince, key)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x1000000000L) != 0) { value = _headers._IfUnmodifiedSince; return true; @@ -1769,7 +1984,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.ProxyAuthorization, key)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x4000000000L) != 0) { value = _headers._ProxyAuthorization; return true; @@ -1778,7 +1993,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.CorrelationContext, key)) { - if ((_bits & 0x80000000000L) != 0) + if ((_bits & 0x800000000000L) != 0) { value = _headers._CorrelationContext; return true; @@ -1788,7 +2003,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.IfUnmodifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x1000000000L) != 0) { value = _headers._IfUnmodifiedSince; return true; @@ -1797,7 +2012,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.ProxyAuthorization.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x4000000000L) != 0) { value = _headers._ProxyAuthorization; return true; @@ -1806,7 +2021,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.CorrelationContext.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000000L) != 0) + if ((_bits & 0x800000000000L) != 0) { value = _headers._CorrelationContext; return true; @@ -1819,7 +2034,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.UpgradeInsecureRequests, key)) { - if ((_bits & 0x20000000000L) != 0) + if ((_bits & 0x200000000000L) != 0) { value = _headers._UpgradeInsecureRequests; return true; @@ -1829,7 +2044,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.UpgradeInsecureRequests.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000000L) != 0) + if ((_bits & 0x200000000000L) != 0) { value = _headers._UpgradeInsecureRequests; return true; @@ -1842,7 +2057,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.AccessControlRequestMethod, key)) { - if ((_bits & 0x800000000000L) != 0) + if ((_bits & 0x8000000000000L) != 0) { value = _headers._AccessControlRequestMethod; return true; @@ -1852,7 +2067,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.AccessControlRequestMethod.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000000000L) != 0) + if ((_bits & 0x8000000000000L) != 0) { value = _headers._AccessControlRequestMethod; return true; @@ -1865,7 +2080,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.AccessControlRequestHeaders, key)) { - if ((_bits & 0x1000000000000L) != 0) + if ((_bits & 0x10000000000000L) != 0) { value = _headers._AccessControlRequestHeaders; return true; @@ -1875,7 +2090,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.AccessControlRequestHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000000000L) != 0) + if ((_bits & 0x10000000000000L) != 0) { value = _headers._AccessControlRequestHeaders; return true; @@ -1897,14 +2112,14 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) } if (ReferenceEquals(HeaderNames.DNT, key)) { - _bits |= 0x10000000000L; + _bits |= 0x100000000000L; _headers._DNT = value; return; } @@ -1933,7 +2148,7 @@ protected override void SetValueFast(string key, StringValues value) } if (HeaderNames.DNT.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x10000000000L; + _bits |= 0x100000000000L; _headers._DNT = value; return; } @@ -1943,7 +2158,7 @@ protected override void SetValueFast(string key, StringValues value) { if (ReferenceEquals(HeaderNames.Host, key)) { - _bits |= 0x8000000L; + _bits |= 0x80000000L; _headers._Host = value; return; } @@ -1955,14 +2170,14 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) } if (HeaderNames.From.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x4000000L; + _bits |= 0x40000000L; _headers._From = value; return; } @@ -1982,6 +2197,12 @@ protected override void SetValueFast(string key, StringValues value) } case 5: { + if (ReferenceEquals(HeaderNames.Path, key)) + { + _bits |= 0x200000L; + _headers._Path = value; + return; + } if (ReferenceEquals(HeaderNames.Allow, key)) { _bits |= 0x400L; @@ -1990,11 +2211,17 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) } if (HeaderNames.Range.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x1000000000L; + _bits |= 0x10000000000L; _headers._Range = value; return; } @@ -2013,7 +2240,7 @@ protected override void SetValueFast(string key, StringValues value) { if (ReferenceEquals(HeaderNames.Accept, key)) { - _bits |= 0x80000L; + _bits |= 0x800000L; _headers._Accept = value; return; } @@ -2025,26 +2252,26 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) } if (HeaderNames.Referer.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x800000000L; + _bits |= 0x8000000000L; _headers._Referer = value; return; } @@ -2143,26 +2394,26 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) _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 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) _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 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) } if (ReferenceEquals(HeaderNames.TraceParent, key)) { - _bits |= 0x100000000000L; + _bits |= 0x1000000000000L; _headers._TraceParent = value; return; } @@ -2273,7 +2536,7 @@ protected override void SetValueFast(string key, StringValues value) } if (HeaderNames.TraceParent.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x100000000000L; + _bits |= 0x1000000000000L; _headers._TraceParent = value; return; } @@ -2289,7 +2552,7 @@ protected override void SetValueFast(string key, StringValues value) } if (ReferenceEquals(HeaderNames.MaxForwards, key)) { - _bits |= 0x200000000L; + _bits |= 0x2000000000L; _headers._MaxForwards = value; return; } @@ -2302,7 +2565,7 @@ protected override void SetValueFast(string key, StringValues value) } if (HeaderNames.MaxForwards.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x200000000L; + _bits |= 0x2000000000L; _headers._MaxForwards = value; return; } @@ -2330,13 +2593,13 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) { if (ReferenceEquals(HeaderNames.AcceptCharset, key)) { - _bits |= 0x100000L; + _bits |= 0x1000000L; _headers._AcceptCharset = value; return; } @@ -2389,7 +2652,7 @@ protected override void SetValueFast(string key, StringValues value) if (HeaderNames.AcceptCharset.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x100000L; + _bits |= 0x1000000L; _headers._AcceptCharset = value; return; } @@ -2404,26 +2667,26 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) } if (ReferenceEquals(HeaderNames.IfModifiedSince, key)) { - _bits |= 0x20000000L; + _bits |= 0x200000000L; _headers._IfModifiedSince = value; return; } @@ -2493,7 +2756,7 @@ protected override void SetValueFast(string key, StringValues value) } if (HeaderNames.IfModifiedSince.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x20000000L; + _bits |= 0x200000000L; _headers._IfModifiedSince = value; return; } @@ -2503,38 +2766,38 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ private void Clear(long bitsToClear) if ((tempBits & 0x80000L) != 0) { - _headers._Accept = default; + _headers._Authority = default; if((tempBits & ~0x80000L) == 0) { return; @@ -4833,24 +5256,64 @@ private void Clear(long bitsToClear) tempBits &= ~0x80000L; } - if ((tempBits & 0x8000000L) != 0) + if ((tempBits & 0x100000L) != 0) + { + _headers._Method = default; + if((tempBits & ~0x100000L) == 0) + { + return; + } + tempBits &= ~0x100000L; + } + + if ((tempBits & 0x200000L) != 0) + { + _headers._Path = default; + if((tempBits & ~0x200000L) == 0) + { + return; + } + 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 & ~0x8000000L) == 0) + if((tempBits & ~0x80000000L) == 0) { return; } - tempBits &= ~0x8000000L; + tempBits &= ~0x80000000L; } - if ((tempBits & 0x8000000000L) != 0) + if ((tempBits & 0x80000000000L) != 0) { _headers._UserAgent = default; - if((tempBits & ~0x8000000000L) == 0) + if((tempBits & ~0x80000000000L) == 0) { return; } - tempBits &= ~0x8000000000L; + tempBits &= ~0x80000000000L; } if ((tempBits & 0x1L) != 0) @@ -5033,274 +5496,274 @@ private void Clear(long bitsToClear) tempBits &= ~0x40000L; } - if ((tempBits & 0x100000L) != 0) + if ((tempBits & 0x1000000L) != 0) { _headers._AcceptCharset = default; - if((tempBits & ~0x100000L) == 0) + if((tempBits & ~0x1000000L) == 0) { return; } - tempBits &= ~0x100000L; + tempBits &= ~0x1000000L; } - if ((tempBits & 0x200000L) != 0) + if ((tempBits & 0x2000000L) != 0) { _headers._AcceptEncoding = default; - if((tempBits & ~0x200000L) == 0) + if((tempBits & ~0x2000000L) == 0) { return; } - tempBits &= ~0x200000L; + tempBits &= ~0x2000000L; } - if ((tempBits & 0x400000L) != 0) + if ((tempBits & 0x4000000L) != 0) { _headers._AcceptLanguage = default; - if((tempBits & ~0x400000L) == 0) + if((tempBits & ~0x4000000L) == 0) { return; } - tempBits &= ~0x400000L; + tempBits &= ~0x4000000L; } - if ((tempBits & 0x800000L) != 0) + if ((tempBits & 0x8000000L) != 0) { _headers._Authorization = default; - if((tempBits & ~0x800000L) == 0) + if((tempBits & ~0x8000000L) == 0) { return; } - tempBits &= ~0x800000L; + tempBits &= ~0x8000000L; } - if ((tempBits & 0x1000000L) != 0) + if ((tempBits & 0x10000000L) != 0) { _headers._Cookie = default; - if((tempBits & ~0x1000000L) == 0) + if((tempBits & ~0x10000000L) == 0) { return; } - tempBits &= ~0x1000000L; + tempBits &= ~0x10000000L; } - if ((tempBits & 0x2000000L) != 0) + if ((tempBits & 0x20000000L) != 0) { _headers._Expect = default; - if((tempBits & ~0x2000000L) == 0) + if((tempBits & ~0x20000000L) == 0) { return; } - tempBits &= ~0x2000000L; + tempBits &= ~0x20000000L; } - if ((tempBits & 0x4000000L) != 0) + if ((tempBits & 0x40000000L) != 0) { _headers._From = default; - if((tempBits & ~0x4000000L) == 0) + if((tempBits & ~0x40000000L) == 0) { return; } - tempBits &= ~0x4000000L; + tempBits &= ~0x40000000L; } - if ((tempBits & 0x10000000L) != 0) + if ((tempBits & 0x100000000L) != 0) { _headers._IfMatch = default; - if((tempBits & ~0x10000000L) == 0) + if((tempBits & ~0x100000000L) == 0) { return; } - tempBits &= ~0x10000000L; + tempBits &= ~0x100000000L; } - if ((tempBits & 0x20000000L) != 0) + if ((tempBits & 0x200000000L) != 0) { _headers._IfModifiedSince = default; - if((tempBits & ~0x20000000L) == 0) + if((tempBits & ~0x200000000L) == 0) { return; } - tempBits &= ~0x20000000L; + tempBits &= ~0x200000000L; } - if ((tempBits & 0x40000000L) != 0) + if ((tempBits & 0x400000000L) != 0) { _headers._IfNoneMatch = default; - if((tempBits & ~0x40000000L) == 0) + if((tempBits & ~0x400000000L) == 0) { return; } - tempBits &= ~0x40000000L; + tempBits &= ~0x400000000L; } - if ((tempBits & 0x80000000L) != 0) + if ((tempBits & 0x800000000L) != 0) { _headers._IfRange = default; - if((tempBits & ~0x80000000L) == 0) + if((tempBits & ~0x800000000L) == 0) { return; } - tempBits &= ~0x80000000L; + tempBits &= ~0x800000000L; } - if ((tempBits & 0x100000000L) != 0) + if ((tempBits & 0x1000000000L) != 0) { _headers._IfUnmodifiedSince = default; - if((tempBits & ~0x100000000L) == 0) + if((tempBits & ~0x1000000000L) == 0) { return; } - tempBits &= ~0x100000000L; + tempBits &= ~0x1000000000L; } - if ((tempBits & 0x200000000L) != 0) + if ((tempBits & 0x2000000000L) != 0) { _headers._MaxForwards = default; - if((tempBits & ~0x200000000L) == 0) + if((tempBits & ~0x2000000000L) == 0) { return; } - tempBits &= ~0x200000000L; + tempBits &= ~0x2000000000L; } - if ((tempBits & 0x400000000L) != 0) + if ((tempBits & 0x4000000000L) != 0) { _headers._ProxyAuthorization = default; - if((tempBits & ~0x400000000L) == 0) + if((tempBits & ~0x4000000000L) == 0) { return; } - tempBits &= ~0x400000000L; + tempBits &= ~0x4000000000L; } - if ((tempBits & 0x800000000L) != 0) + if ((tempBits & 0x8000000000L) != 0) { _headers._Referer = default; - if((tempBits & ~0x800000000L) == 0) + if((tempBits & ~0x8000000000L) == 0) { return; } - tempBits &= ~0x800000000L; + tempBits &= ~0x8000000000L; } - if ((tempBits & 0x1000000000L) != 0) + if ((tempBits & 0x10000000000L) != 0) { _headers._Range = default; - if((tempBits & ~0x1000000000L) == 0) + if((tempBits & ~0x10000000000L) == 0) { return; } - tempBits &= ~0x1000000000L; + tempBits &= ~0x10000000000L; } - if ((tempBits & 0x2000000000L) != 0) + if ((tempBits & 0x20000000000L) != 0) { _headers._TE = default; - if((tempBits & ~0x2000000000L) == 0) + if((tempBits & ~0x20000000000L) == 0) { return; } - tempBits &= ~0x2000000000L; + tempBits &= ~0x20000000000L; } - if ((tempBits & 0x4000000000L) != 0) + if ((tempBits & 0x40000000000L) != 0) { _headers._Translate = default; - if((tempBits & ~0x4000000000L) == 0) + if((tempBits & ~0x40000000000L) == 0) { return; } - tempBits &= ~0x4000000000L; + tempBits &= ~0x40000000000L; } - if ((tempBits & 0x10000000000L) != 0) + if ((tempBits & 0x100000000000L) != 0) { _headers._DNT = default; - if((tempBits & ~0x10000000000L) == 0) + if((tempBits & ~0x100000000000L) == 0) { return; } - tempBits &= ~0x10000000000L; + tempBits &= ~0x100000000000L; } - if ((tempBits & 0x20000000000L) != 0) + if ((tempBits & 0x200000000000L) != 0) { _headers._UpgradeInsecureRequests = default; - if((tempBits & ~0x20000000000L) == 0) + if((tempBits & ~0x200000000000L) == 0) { return; } - tempBits &= ~0x20000000000L; + tempBits &= ~0x200000000000L; } - if ((tempBits & 0x40000000000L) != 0) + if ((tempBits & 0x400000000000L) != 0) { _headers._RequestId = default; - if((tempBits & ~0x40000000000L) == 0) + if((tempBits & ~0x400000000000L) == 0) { return; } - tempBits &= ~0x40000000000L; + tempBits &= ~0x400000000000L; } - if ((tempBits & 0x80000000000L) != 0) + if ((tempBits & 0x800000000000L) != 0) { _headers._CorrelationContext = default; - if((tempBits & ~0x80000000000L) == 0) + if((tempBits & ~0x800000000000L) == 0) { return; } - tempBits &= ~0x80000000000L; + tempBits &= ~0x800000000000L; } - if ((tempBits & 0x100000000000L) != 0) + if ((tempBits & 0x1000000000000L) != 0) { _headers._TraceParent = default; - if((tempBits & ~0x100000000000L) == 0) + if((tempBits & ~0x1000000000000L) == 0) { return; } - tempBits &= ~0x100000000000L; + tempBits &= ~0x1000000000000L; } - if ((tempBits & 0x200000000000L) != 0) + if ((tempBits & 0x2000000000000L) != 0) { _headers._TraceState = default; - if((tempBits & ~0x200000000000L) == 0) + if((tempBits & ~0x2000000000000L) == 0) { return; } - tempBits &= ~0x200000000000L; + tempBits &= ~0x2000000000000L; } - if ((tempBits & 0x400000000000L) != 0) + if ((tempBits & 0x4000000000000L) != 0) { _headers._Origin = default; - if((tempBits & ~0x400000000000L) == 0) + if((tempBits & ~0x4000000000000L) == 0) { return; } - tempBits &= ~0x400000000000L; + tempBits &= ~0x4000000000000L; } - if ((tempBits & 0x800000000000L) != 0) + if ((tempBits & 0x8000000000000L) != 0) { _headers._AccessControlRequestMethod = default; - if((tempBits & ~0x800000000000L) == 0) + if((tempBits & ~0x8000000000000L) == 0) { return; } - tempBits &= ~0x800000000000L; + tempBits &= ~0x8000000000000L; } - if ((tempBits & 0x1000000000000L) != 0) + if ((tempBits & 0x10000000000000L) != 0) { _headers._AccessControlRequestHeaders = default; - if((tempBits & ~0x1000000000000L) == 0) + if((tempBits & ~0x10000000000000L) == 0) { return; } - tempBits &= ~0x1000000000000L; + tempBits &= ~0x10000000000000L; } } @@ -5489,7 +5952,7 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i } [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 @@ public unsafe void Append(Span name, Span value) case 2: if (((Unsafe.ReadUnaligned(ref nameStart) & 0xdfdfu) == 0x4554u)) { - flag = 0x2000000000L; + flag = 0x20000000000L; values = ref _headers._TE; } break; @@ -5788,7 +6287,7 @@ public unsafe void Append(Span name, Span value) 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 @@ public unsafe void Append(Span name, Span value) 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 @@ public unsafe void Append(Span name, Span value) } 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 @@ public unsafe void Append(Span name, Span value) 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 @@ public unsafe void Append(Span name, Span value) } 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 @@ public unsafe void Append(Span name, Span value) 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 @@ public unsafe void Append(Span name, Span value) } 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 @@ public unsafe void Append(Span name, Span value) } 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 @@ public unsafe void Append(Span name, Span value) } 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 @@ public unsafe void Append(Span name, Span value) } 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 @@ public unsafe void Append(Span name, Span value) 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 @@ public unsafe void Append(Span name, Span value) 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 @@ public unsafe void Append(Span name, Span value) 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 @@ public unsafe void Append(Span name, Span value) 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 @@ public unsafe void Append(Span name, Span value) } // 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 @@ public unsafe void Append(Span name, Span value) // 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 @@ private struct HeaderReferences 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 @@ public bool MoveNext() 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 HeaderIfRange; + case 36: + goto HeaderIfUnmodifiedSince; + case 37: + goto HeaderMaxForwards; + case 38: + goto HeaderProxyAuthorization; + case 39: goto HeaderReferer; - case 36: + case 40: goto HeaderRange; - case 37: + case 41: goto HeaderTE; - case 38: + case 42: goto HeaderTranslate; - case 39: + case 43: goto HeaderUserAgent; - case 40: + case 44: goto HeaderDNT; - case 41: + case 45: goto HeaderUpgradeInsecureRequests; - case 42: + case 46: goto HeaderRequestId; - case 43: + case 47: goto HeaderCorrelationContext; - case 44: + case 48: goto HeaderTraceParent; - case 45: + case 49: goto HeaderTraceState; - case 46: + case 50: goto HeaderOrigin; - case 47: + case 51: goto HeaderAccessControlRequestMethod; - case 48: + case 52: goto HeaderAccessControlRequestHeaders; - case 49: + case 53: goto HeaderContentLength; default: goto ExtraHeaders; @@ -6297,6 +6826,7 @@ public bool MoveNext() if ((_bits & 0x1L) != 0) { _current = new KeyValuePair(HeaderNames.CacheControl, _collection._headers._CacheControl); + _currentKnownType = KnownHeaderType.CacheControl; _next = 1; return true; } @@ -6304,6 +6834,7 @@ public bool MoveNext() if ((_bits & 0x2L) != 0) { _current = new KeyValuePair(HeaderNames.Connection, _collection._headers._Connection); + _currentKnownType = KnownHeaderType.Connection; _next = 2; return true; } @@ -6311,6 +6842,7 @@ public bool MoveNext() if ((_bits & 0x4L) != 0) { _current = new KeyValuePair(HeaderNames.Date, _collection._headers._Date); + _currentKnownType = KnownHeaderType.Date; _next = 3; return true; } @@ -6318,6 +6850,7 @@ public bool MoveNext() if ((_bits & 0x8L) != 0) { _current = new KeyValuePair(HeaderNames.KeepAlive, _collection._headers._KeepAlive); + _currentKnownType = KnownHeaderType.KeepAlive; _next = 4; return true; } @@ -6325,6 +6858,7 @@ public bool MoveNext() if ((_bits & 0x10L) != 0) { _current = new KeyValuePair(HeaderNames.Pragma, _collection._headers._Pragma); + _currentKnownType = KnownHeaderType.Pragma; _next = 5; return true; } @@ -6332,6 +6866,7 @@ public bool MoveNext() if ((_bits & 0x20L) != 0) { _current = new KeyValuePair(HeaderNames.Trailer, _collection._headers._Trailer); + _currentKnownType = KnownHeaderType.Trailer; _next = 6; return true; } @@ -6339,6 +6874,7 @@ public bool MoveNext() if ((_bits & 0x40L) != 0) { _current = new KeyValuePair(HeaderNames.TransferEncoding, _collection._headers._TransferEncoding); + _currentKnownType = KnownHeaderType.TransferEncoding; _next = 7; return true; } @@ -6346,6 +6882,7 @@ public bool MoveNext() if ((_bits & 0x80L) != 0) { _current = new KeyValuePair(HeaderNames.Upgrade, _collection._headers._Upgrade); + _currentKnownType = KnownHeaderType.Upgrade; _next = 8; return true; } @@ -6353,6 +6890,7 @@ public bool MoveNext() if ((_bits & 0x100L) != 0) { _current = new KeyValuePair(HeaderNames.Via, _collection._headers._Via); + _currentKnownType = KnownHeaderType.Via; _next = 9; return true; } @@ -6360,6 +6898,7 @@ public bool MoveNext() if ((_bits & 0x200L) != 0) { _current = new KeyValuePair(HeaderNames.Warning, _collection._headers._Warning); + _currentKnownType = KnownHeaderType.Warning; _next = 10; return true; } @@ -6367,6 +6906,7 @@ public bool MoveNext() if ((_bits & 0x400L) != 0) { _current = new KeyValuePair(HeaderNames.Allow, _collection._headers._Allow); + _currentKnownType = KnownHeaderType.Allow; _next = 11; return true; } @@ -6374,6 +6914,7 @@ public bool MoveNext() if ((_bits & 0x800L) != 0) { _current = new KeyValuePair(HeaderNames.ContentType, _collection._headers._ContentType); + _currentKnownType = KnownHeaderType.ContentType; _next = 12; return true; } @@ -6381,6 +6922,7 @@ public bool MoveNext() if ((_bits & 0x1000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentEncoding, _collection._headers._ContentEncoding); + _currentKnownType = KnownHeaderType.ContentEncoding; _next = 13; return true; } @@ -6388,6 +6930,7 @@ public bool MoveNext() if ((_bits & 0x2000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentLanguage, _collection._headers._ContentLanguage); + _currentKnownType = KnownHeaderType.ContentLanguage; _next = 14; return true; } @@ -6395,6 +6938,7 @@ public bool MoveNext() if ((_bits & 0x4000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentLocation, _collection._headers._ContentLocation); + _currentKnownType = KnownHeaderType.ContentLocation; _next = 15; return true; } @@ -6402,6 +6946,7 @@ public bool MoveNext() if ((_bits & 0x8000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentMD5, _collection._headers._ContentMD5); + _currentKnownType = KnownHeaderType.ContentMD5; _next = 16; return true; } @@ -6409,6 +6954,7 @@ public bool MoveNext() if ((_bits & 0x10000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentRange, _collection._headers._ContentRange); + _currentKnownType = KnownHeaderType.ContentRange; _next = 17; return true; } @@ -6416,6 +6962,7 @@ public bool MoveNext() if ((_bits & 0x20000L) != 0) { _current = new KeyValuePair(HeaderNames.Expires, _collection._headers._Expires); + _currentKnownType = KnownHeaderType.Expires; _next = 18; return true; } @@ -6423,233 +6970,299 @@ public bool MoveNext() 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 @@ internal partial class HttpResponseHeaders { 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,20 +7642,37 @@ public StringValues HeaderAge _headers._Age = value; } } - public StringValues HeaderETag + public StringValues HeaderAltSvc { get { StringValues value = default; if ((_bits & 0x200000L) != 0) { - value = _headers._ETag; + 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; + } + return value; + } + set + { + _bits |= 0x400000L; _headers._ETag = value; } } @@ -7051,7 +7681,7 @@ public StringValues HeaderLocation get { StringValues value = default; - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x800000L) != 0) { value = _headers._Location; } @@ -7059,7 +7689,7 @@ public StringValues HeaderLocation } set { - _bits |= 0x400000L; + _bits |= 0x800000L; _headers._Location = value; } } @@ -7068,7 +7698,7 @@ public StringValues HeaderProxyAuthenticate get { StringValues value = default; - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._ProxyAuthenticate; } @@ -7076,7 +7706,7 @@ public StringValues HeaderProxyAuthenticate } set { - _bits |= 0x800000L; + _bits |= 0x1000000L; _headers._ProxyAuthenticate = value; } } @@ -7085,7 +7715,7 @@ public StringValues HeaderRetryAfter get { StringValues value = default; - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._RetryAfter; } @@ -7093,7 +7723,7 @@ public StringValues HeaderRetryAfter } set { - _bits |= 0x1000000L; + _bits |= 0x2000000L; _headers._RetryAfter = value; } } @@ -7102,7 +7732,7 @@ public StringValues HeaderServer get { StringValues value = default; - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._Server; } @@ -7110,7 +7740,7 @@ public StringValues HeaderServer } set { - _bits |= 0x2000000L; + _bits |= 0x4000000L; _headers._Server = value; _headers._rawServer = null; } @@ -7120,7 +7750,7 @@ public StringValues HeaderSetCookie get { StringValues value = default; - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._SetCookie; } @@ -7128,7 +7758,7 @@ public StringValues HeaderSetCookie } set { - _bits |= 0x4000000L; + _bits |= 0x8000000L; _headers._SetCookie = value; } } @@ -7137,7 +7767,7 @@ public StringValues HeaderVary get { StringValues value = default; - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Vary; } @@ -7145,7 +7775,7 @@ public StringValues HeaderVary } set { - _bits |= 0x8000000L; + _bits |= 0x10000000L; _headers._Vary = value; } } @@ -7154,7 +7784,7 @@ public StringValues HeaderWWWAuthenticate get { StringValues value = default; - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._WWWAuthenticate; } @@ -7162,7 +7792,7 @@ public StringValues HeaderWWWAuthenticate } set { - _bits |= 0x10000000L; + _bits |= 0x20000000L; _headers._WWWAuthenticate = value; } } @@ -7171,7 +7801,7 @@ public StringValues HeaderAccessControlAllowCredentials get { StringValues value = default; - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._AccessControlAllowCredentials; } @@ -7179,7 +7809,7 @@ public StringValues HeaderAccessControlAllowCredentials } set { - _bits |= 0x20000000L; + _bits |= 0x40000000L; _headers._AccessControlAllowCredentials = value; } } @@ -7188,7 +7818,7 @@ public StringValues HeaderAccessControlAllowHeaders get { StringValues value = default; - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._AccessControlAllowHeaders; } @@ -7196,7 +7826,7 @@ public StringValues HeaderAccessControlAllowHeaders } set { - _bits |= 0x40000000L; + _bits |= 0x80000000L; _headers._AccessControlAllowHeaders = value; } } @@ -7205,7 +7835,7 @@ public StringValues HeaderAccessControlAllowMethods get { StringValues value = default; - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._AccessControlAllowMethods; } @@ -7213,7 +7843,7 @@ public StringValues HeaderAccessControlAllowMethods } set { - _bits |= 0x80000000L; + _bits |= 0x100000000L; _headers._AccessControlAllowMethods = value; } } @@ -7222,7 +7852,7 @@ public StringValues HeaderAccessControlAllowOrigin get { StringValues value = default; - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._AccessControlAllowOrigin; } @@ -7230,7 +7860,7 @@ public StringValues HeaderAccessControlAllowOrigin } set { - _bits |= 0x100000000L; + _bits |= 0x200000000L; _headers._AccessControlAllowOrigin = value; } } @@ -7239,7 +7869,7 @@ public StringValues HeaderAccessControlExposeHeaders get { StringValues value = default; - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._AccessControlExposeHeaders; } @@ -7247,7 +7877,7 @@ public StringValues HeaderAccessControlExposeHeaders } set { - _bits |= 0x200000000L; + _bits |= 0x400000000L; _headers._AccessControlExposeHeaders = value; } } @@ -7256,7 +7886,7 @@ public StringValues HeaderAccessControlMaxAge get { StringValues value = default; - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._AccessControlMaxAge; } @@ -7264,7 +7894,7 @@ public StringValues HeaderAccessControlMaxAge } set { - _bits |= 0x400000000L; + _bits |= 0x800000000L; _headers._AccessControlMaxAge = value; } } @@ -7305,7 +7935,7 @@ public void SetRawTransferEncoding(StringValues value, byte[] raw) } public void SetRawServer(StringValues value, byte[] raw) { - _bits |= 0x2000000L; + _bits |= 0x4000000L; _headers._Server = value; _headers._rawServer = raw; } @@ -7373,7 +8003,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.ETag, key)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x400000L) != 0) { value = _headers._ETag; return true; @@ -7382,7 +8012,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.Vary, key)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Vary; return true; @@ -7401,7 +8031,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.ETag.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000L) != 0) + if ((_bits & 0x400000L) != 0) { value = _headers._ETag; return true; @@ -7410,7 +8040,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.Vary.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x8000000L) != 0) + if ((_bits & 0x10000000L) != 0) { value = _headers._Vary; return true; @@ -7446,7 +8076,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.Server, key)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._Server; return true; @@ -7465,7 +8095,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.Server.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x2000000L) != 0) + if ((_bits & 0x4000000L) != 0) { value = _headers._Server; return true; @@ -7521,6 +8151,15 @@ protected override bool TryGetValueFast(string key, out StringValues value) } 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 @@ protected override bool TryGetValueFast(string key, out StringValues value) } 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 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.Location.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000L) != 0) + if ((_bits & 0x800000L) != 0) { value = _headers._Location; return true; @@ -7605,7 +8253,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.SetCookie, key)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._SetCookie; return true; @@ -7633,7 +8281,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.SetCookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x4000000L) != 0) + if ((_bits & 0x8000000L) != 0) { value = _headers._SetCookie; return true; @@ -7655,7 +8303,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.RetryAfter, key)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._RetryAfter; return true; @@ -7674,7 +8322,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.RetryAfter.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x1000000L) != 0) + if ((_bits & 0x2000000L) != 0) { value = _headers._RetryAfter; return true; @@ -7837,7 +8485,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.WWWAuthenticate, key)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._WWWAuthenticate; return true; @@ -7874,7 +8522,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.WWWAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x10000000L) != 0) + if ((_bits & 0x20000000L) != 0) { value = _headers._WWWAuthenticate; return true; @@ -7910,7 +8558,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.ProxyAuthenticate, key)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._ProxyAuthenticate; return true; @@ -7920,7 +8568,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.ProxyAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x800000L) != 0) + if ((_bits & 0x1000000L) != 0) { value = _headers._ProxyAuthenticate; return true; @@ -7933,7 +8581,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.AccessControlMaxAge, key)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._AccessControlMaxAge; return true; @@ -7943,7 +8591,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.AccessControlMaxAge.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x400000000L) != 0) + if ((_bits & 0x800000000L) != 0) { value = _headers._AccessControlMaxAge; return true; @@ -7956,7 +8604,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.AccessControlAllowOrigin, key)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._AccessControlAllowOrigin; return true; @@ -7966,7 +8614,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.AccessControlAllowOrigin.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x100000000L) != 0) + if ((_bits & 0x200000000L) != 0) { value = _headers._AccessControlAllowOrigin; return true; @@ -7979,7 +8627,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.AccessControlAllowHeaders, key)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._AccessControlAllowHeaders; return true; @@ -7988,7 +8636,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (ReferenceEquals(HeaderNames.AccessControlAllowMethods, key)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._AccessControlAllowMethods; return true; @@ -7998,7 +8646,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.AccessControlAllowHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x40000000L) != 0) + if ((_bits & 0x80000000L) != 0) { value = _headers._AccessControlAllowHeaders; return true; @@ -8007,7 +8655,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) } if (HeaderNames.AccessControlAllowMethods.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x80000000L) != 0) + if ((_bits & 0x100000000L) != 0) { value = _headers._AccessControlAllowMethods; return true; @@ -8020,7 +8668,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.AccessControlExposeHeaders, key)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._AccessControlExposeHeaders; return true; @@ -8030,7 +8678,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.AccessControlExposeHeaders.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x200000000L) != 0) + if ((_bits & 0x400000000L) != 0) { value = _headers._AccessControlExposeHeaders; return true; @@ -8043,7 +8691,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) { if (ReferenceEquals(HeaderNames.AccessControlAllowCredentials, key)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._AccessControlAllowCredentials; return true; @@ -8053,7 +8701,7 @@ protected override bool TryGetValueFast(string key, out StringValues value) if (HeaderNames.AccessControlAllowCredentials.Equals(key, StringComparison.OrdinalIgnoreCase)) { - if ((_bits & 0x20000000L) != 0) + if ((_bits & 0x40000000L) != 0) { value = _headers._AccessControlAllowCredentials; return true; @@ -8112,13 +8760,13 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) } 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 @@ protected override void SetValueFast(string key, StringValues value) { if (ReferenceEquals(HeaderNames.Server, key)) { - _bits |= 0x2000000L; + _bits |= 0x4000000L; _headers._Server = value; _headers._rawServer = null; return; @@ -8179,7 +8827,7 @@ protected override void SetValueFast(string key, StringValues value) if (HeaderNames.Server.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x2000000L; + _bits |= 0x4000000L; _headers._Server = value; _headers._rawServer = null; return; @@ -8214,8 +8862,14 @@ protected override void SetValueFast(string key, StringValues value) } if (ReferenceEquals(HeaderNames.Expires, key)) { - _bits |= 0x20000L; - _headers._Expires = value; + _bits |= 0x20000L; + _headers._Expires = value; + return; + } + if (ReferenceEquals(HeaderNames.AltSvc, key)) + { + _bits |= 0x200000L; + _headers._AltSvc = value; return; } @@ -8243,20 +8897,26 @@ protected override void SetValueFast(string key, StringValues value) _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 @@ protected override void SetValueFast(string key, StringValues value) } if (ReferenceEquals(HeaderNames.SetCookie, key)) { - _bits |= 0x4000000L; + _bits |= 0x8000000L; _headers._SetCookie = value; return; } @@ -8299,7 +8959,7 @@ protected override void SetValueFast(string key, StringValues value) } if (HeaderNames.SetCookie.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x4000000L; + _bits |= 0x8000000L; _headers._SetCookie = value; return; } @@ -8315,7 +8975,7 @@ protected override void SetValueFast(string key, StringValues value) } if (ReferenceEquals(HeaderNames.RetryAfter, key)) { - _bits |= 0x1000000L; + _bits |= 0x2000000L; _headers._RetryAfter = value; return; } @@ -8328,7 +8988,7 @@ protected override void SetValueFast(string key, StringValues value) } if (HeaderNames.RetryAfter.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x1000000L; + _bits |= 0x2000000L; _headers._RetryAfter = value; return; } @@ -8441,7 +9101,7 @@ protected override void SetValueFast(string key, StringValues value) } if (ReferenceEquals(HeaderNames.WWWAuthenticate, key)) { - _bits |= 0x10000000L; + _bits |= 0x20000000L; _headers._WWWAuthenticate = value; return; } @@ -8466,7 +9126,7 @@ protected override void SetValueFast(string key, StringValues value) } if (HeaderNames.WWWAuthenticate.Equals(key, StringComparison.OrdinalIgnoreCase)) { - _bits |= 0x10000000L; + _bits |= 0x20000000L; _headers._WWWAuthenticate = value; return; } @@ -8495,14 +9155,14 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override void SetValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) } 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool AddValueFast(string key, StringValues value) { 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 @@ protected override bool AddValueFast(string key, StringValues value) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) } 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override bool RemoveFast(string key) { 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 @@ protected override bool RemoveFast(string key) 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 @@ protected override void ClearFast() 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 @@ protected override void ClearFast() if ((tempBits & 0x200000L) != 0) { - _headers._ETag = default; + _headers._AltSvc = default; if((tempBits & ~0x200000L) == 0) { return; @@ -10514,7 +11214,7 @@ protected override void ClearFast() if ((tempBits & 0x400000L) != 0) { - _headers._Location = default; + _headers._ETag = default; if((tempBits & ~0x400000L) == 0) { return; @@ -10524,7 +11224,7 @@ protected override void ClearFast() if ((tempBits & 0x800000L) != 0) { - _headers._ProxyAuthenticate = default; + _headers._Location = default; if((tempBits & ~0x800000L) == 0) { return; @@ -10534,7 +11234,7 @@ protected override void ClearFast() if ((tempBits & 0x1000000L) != 0) { - _headers._RetryAfter = default; + _headers._ProxyAuthenticate = default; if((tempBits & ~0x1000000L) == 0) { return; @@ -10542,19 +11242,19 @@ protected override void ClearFast() 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 @@ protected override void ClearFast() if ((tempBits & 0x10000000L) != 0) { - _headers._WWWAuthenticate = default; + _headers._Vary = default; if((tempBits & ~0x10000000L) == 0) { return; @@ -10574,7 +11274,7 @@ protected override void ClearFast() if ((tempBits & 0x20000000L) != 0) { - _headers._AccessControlAllowCredentials = default; + _headers._WWWAuthenticate = default; if((tempBits & ~0x20000000L) == 0) { return; @@ -10584,7 +11284,7 @@ protected override void ClearFast() if ((tempBits & 0x40000000L) != 0) { - _headers._AccessControlAllowHeaders = default; + _headers._AccessControlAllowCredentials = default; if((tempBits & ~0x40000000L) == 0) { return; @@ -10594,7 +11294,7 @@ protected override void ClearFast() if ((tempBits & 0x80000000L) != 0) { - _headers._AccessControlAllowMethods = default; + _headers._AccessControlAllowHeaders = default; if((tempBits & ~0x80000000L) == 0) { return; @@ -10604,7 +11304,7 @@ protected override void ClearFast() if ((tempBits & 0x100000000L) != 0) { - _headers._AccessControlAllowOrigin = default; + _headers._AccessControlAllowMethods = default; if((tempBits & ~0x100000000L) == 0) { return; @@ -10614,7 +11314,7 @@ protected override void ClearFast() if ((tempBits & 0x200000000L) != 0) { - _headers._AccessControlExposeHeaders = default; + _headers._AccessControlAllowOrigin = default; if((tempBits & ~0x200000000L) == 0) { return; @@ -10624,7 +11324,7 @@ protected override void ClearFast() if ((tempBits & 0x400000000L) != 0) { - _headers._AccessControlMaxAge = default; + _headers._AccessControlExposeHeaders = default; if((tempBits & ~0x400000000L) == 0) { return; @@ -10632,6 +11332,16 @@ protected override void ClearFast() 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ protected override bool CopyToFast(KeyValuePair[] array, i { 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 @@ internal unsafe void CopyToFast(ref BufferWriter output) } 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 @@ internal unsafe void CopyToFast(ref BufferWriter output) else { values = ref _headers._Server; - keyStart = 350; + keyStart = 361; keyLength = 10; next = 4; break; // OutputHeader @@ -11051,7 +11770,7 @@ internal unsafe void CopyToFast(ref BufferWriter output) 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,146 +11983,157 @@ internal unsafe void CopyToFast(ref BufferWriter output) 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._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 = 566; + keyStart = 577; keyLength = 26; - next = 36; + next = 37; break; // OutputHeader } return; @@ -11421,7 +12151,7 @@ internal unsafe void CopyToFast(ref BufferWriter output) if (value != null) { output.Write(headerKey); - output.WriteAsciiNoValidation(value); + output.WriteAscii(value); } } } @@ -11451,6 +12181,7 @@ private struct HeaderReferences 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 @@ public bool MoveNext() 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 @@ public bool MoveNext() if ((_bits & 0x1L) != 0) { _current = new KeyValuePair(HeaderNames.CacheControl, _collection._headers._CacheControl); + _currentKnownType = KnownHeaderType.CacheControl; _next = 1; return true; } @@ -11566,6 +12300,7 @@ public bool MoveNext() if ((_bits & 0x2L) != 0) { _current = new KeyValuePair(HeaderNames.Connection, _collection._headers._Connection); + _currentKnownType = KnownHeaderType.Connection; _next = 2; return true; } @@ -11573,6 +12308,7 @@ public bool MoveNext() if ((_bits & 0x4L) != 0) { _current = new KeyValuePair(HeaderNames.Date, _collection._headers._Date); + _currentKnownType = KnownHeaderType.Date; _next = 3; return true; } @@ -11580,6 +12316,7 @@ public bool MoveNext() if ((_bits & 0x8L) != 0) { _current = new KeyValuePair(HeaderNames.KeepAlive, _collection._headers._KeepAlive); + _currentKnownType = KnownHeaderType.KeepAlive; _next = 4; return true; } @@ -11587,6 +12324,7 @@ public bool MoveNext() if ((_bits & 0x10L) != 0) { _current = new KeyValuePair(HeaderNames.Pragma, _collection._headers._Pragma); + _currentKnownType = KnownHeaderType.Pragma; _next = 5; return true; } @@ -11594,6 +12332,7 @@ public bool MoveNext() if ((_bits & 0x20L) != 0) { _current = new KeyValuePair(HeaderNames.Trailer, _collection._headers._Trailer); + _currentKnownType = KnownHeaderType.Trailer; _next = 6; return true; } @@ -11601,6 +12340,7 @@ public bool MoveNext() if ((_bits & 0x40L) != 0) { _current = new KeyValuePair(HeaderNames.TransferEncoding, _collection._headers._TransferEncoding); + _currentKnownType = KnownHeaderType.TransferEncoding; _next = 7; return true; } @@ -11608,6 +12348,7 @@ public bool MoveNext() if ((_bits & 0x80L) != 0) { _current = new KeyValuePair(HeaderNames.Upgrade, _collection._headers._Upgrade); + _currentKnownType = KnownHeaderType.Upgrade; _next = 8; return true; } @@ -11615,6 +12356,7 @@ public bool MoveNext() if ((_bits & 0x100L) != 0) { _current = new KeyValuePair(HeaderNames.Via, _collection._headers._Via); + _currentKnownType = KnownHeaderType.Via; _next = 9; return true; } @@ -11622,6 +12364,7 @@ public bool MoveNext() if ((_bits & 0x200L) != 0) { _current = new KeyValuePair(HeaderNames.Warning, _collection._headers._Warning); + _currentKnownType = KnownHeaderType.Warning; _next = 10; return true; } @@ -11629,6 +12372,7 @@ public bool MoveNext() if ((_bits & 0x400L) != 0) { _current = new KeyValuePair(HeaderNames.Allow, _collection._headers._Allow); + _currentKnownType = KnownHeaderType.Allow; _next = 11; return true; } @@ -11636,6 +12380,7 @@ public bool MoveNext() if ((_bits & 0x800L) != 0) { _current = new KeyValuePair(HeaderNames.ContentType, _collection._headers._ContentType); + _currentKnownType = KnownHeaderType.ContentType; _next = 12; return true; } @@ -11643,6 +12388,7 @@ public bool MoveNext() if ((_bits & 0x1000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentEncoding, _collection._headers._ContentEncoding); + _currentKnownType = KnownHeaderType.ContentEncoding; _next = 13; return true; } @@ -11650,6 +12396,7 @@ public bool MoveNext() if ((_bits & 0x2000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentLanguage, _collection._headers._ContentLanguage); + _currentKnownType = KnownHeaderType.ContentLanguage; _next = 14; return true; } @@ -11657,6 +12404,7 @@ public bool MoveNext() if ((_bits & 0x4000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentLocation, _collection._headers._ContentLocation); + _currentKnownType = KnownHeaderType.ContentLocation; _next = 15; return true; } @@ -11664,6 +12412,7 @@ public bool MoveNext() if ((_bits & 0x8000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentMD5, _collection._headers._ContentMD5); + _currentKnownType = KnownHeaderType.ContentMD5; _next = 16; return true; } @@ -11671,6 +12420,7 @@ public bool MoveNext() if ((_bits & 0x10000L) != 0) { _current = new KeyValuePair(HeaderNames.ContentRange, _collection._headers._ContentRange); + _currentKnownType = KnownHeaderType.ContentRange; _next = 17; return true; } @@ -11678,6 +12428,7 @@ public bool MoveNext() if ((_bits & 0x20000L) != 0) { _current = new KeyValuePair(HeaderNames.Expires, _collection._headers._Expires); + _currentKnownType = KnownHeaderType.Expires; _next = 18; return true; } @@ -11685,6 +12436,7 @@ public bool MoveNext() if ((_bits & 0x40000L) != 0) { _current = new KeyValuePair(HeaderNames.LastModified, _collection._headers._LastModified); + _currentKnownType = KnownHeaderType.LastModified; _next = 19; return true; } @@ -11692,6 +12444,7 @@ public bool MoveNext() if ((_bits & 0x80000L) != 0) { _current = new KeyValuePair(HeaderNames.AcceptRanges, _collection._headers._AcceptRanges); + _currentKnownType = KnownHeaderType.AcceptRanges; _next = 20; return true; } @@ -11699,121 +12452,147 @@ public bool MoveNext() 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 @@ public bool MoveNext() if ((_bits & 0x1L) != 0) { _current = new KeyValuePair(HeaderNames.ETag, _collection._headers._ETag); + _currentKnownType = KnownHeaderType.ETag; _next = 1; return true; } @@ -12067,9 +12847,11 @@ public bool MoveNext() 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 d041705a196c..e1175fcde98f 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 @@ public long? ContentLength 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 2fe5dfdb36be..ff905ca07962 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 @@ public HttpParser(bool showErrorDetails) 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 @@ public unsafe bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence 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 @@ public unsafe bool ParseHeaders(TRequestHandler handler, ref SequenceReader GetUnknownMethod(byte* data, int length, out int metho 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 88ae92e0c0c2..8841be09fa35 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 @@ internal partial class HttpProtocol : IHttpRequestFeature, 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 514a1ef7a51e..f6833a47486c 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 @@ object IFeatureCollection.this[Type key] 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 @@ object IFeatureCollection.this[Type key] { _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 @@ private IEnumerable> FastEnumerable() { 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 0dc3170b0821..539c386efadc 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.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 @@ internal abstract partial class HttpProtocol : IHttpResponseControl 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 @@ internal abstract partial class HttpProtocol : IHttpResponseControl 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 @@ internal abstract partial class HttpProtocol : IHttpResponseControl 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 @@ public HttpProtocol(HttpConnectionContext context) 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 @@ public string HttpVersion { get { - if (_httpVersion == Http.HttpVersion.Http11) + if (_httpVersion == Http.HttpVersion.Http3) { - return HttpUtilities.Http11Version; + return AspNetCore.Http.HttpProtocol.Http3; } - if (_httpVersion == Http.HttpVersion.Http10) + if (_httpVersion == Http.HttpVersion.Http2) { - return HttpUtilities.Http10Version; + return AspNetCore.Http.HttpProtocol.Http2; } - if (_httpVersion == Http.HttpVersion.Http2) + if (_httpVersion == Http.HttpVersion.Http11) { - return HttpUtilities.Http2Version; + return AspNetCore.Http.HttpProtocol.Http11; + } + if (_httpVersion == Http.HttpVersion.Http10) + { + return AspNetCore.Http.HttpProtocol.Http10; } return string.Empty; @@ -163,17 +169,21 @@ public string HttpVersion { // 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.Http11; + _httpVersion = Http.HttpVersion.Http3; } - else if (ReferenceEquals(value, HttpUtilities.Http10Version)) + else if (ReferenceEquals(value, AspNetCore.Http.HttpProtocol.Http2)) { - _httpVersion = Http.HttpVersion.Http10; + _httpVersion = Http.HttpVersion.Http2; } - else if (ReferenceEquals(value, HttpUtilities.Http2Version)) + else if (ReferenceEquals(value, AspNetCore.Http.HttpProtocol.Http11)) { - _httpVersion = Http.HttpVersion.Http2; + _httpVersion = Http.HttpVersion.Http11; + } + else if (ReferenceEquals(value, AspNetCore.Http.HttpProtocol.Http10)) + { + _httpVersion = Http.HttpVersion.Http10; } else { @@ -185,17 +195,21 @@ public string HttpVersion [MethodImpl(MethodImplOptions.NoInlining)] private void HttpVersionSetSlow(string value) { - if (value == HttpUtilities.Http11Version) + if (AspNetCore.Http.HttpProtocol.IsHttp3(value)) { - _httpVersion = Http.HttpVersion.Http11; + _httpVersion = Http.HttpVersion.Http3; } - else if (value == HttpUtilities.Http10Version) + else if (AspNetCore.Http.HttpProtocol.IsHttp2(value)) { - _httpVersion = Http.HttpVersion.Http10; + _httpVersion = Http.HttpVersion.Http2; } - else if (value == HttpUtilities.Http2Version) + else if (AspNetCore.Http.HttpProtocol.IsHttp11(value)) { - _httpVersion = Http.HttpVersion.Http2; + _httpVersion = Http.HttpVersion.Http11; + } + else if (AspNetCore.Http.HttpProtocol.IsHttp10(value)) + { + _httpVersion = Http.HttpVersion.Http10; } else { @@ -290,7 +304,7 @@ public CancellationToken RequestAborted 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 @@ public void Reset() 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 @@ public void Reset() 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 @@ private void PreventRequestAbortedCancellation() } } - 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 @@ public void OnHeader(Span name, Span value) 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 @@ protected Task ProduceEnd() 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 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted) responseHeaders.SetReadOnly(); - if (!hasConnection && _httpVersion != Http.HttpVersion.Http2) + if (!hasConnection && _httpVersion < Http.HttpVersion.Http2) { if (!_keepAlive) { @@ -1171,6 +1188,19 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted) } } + // 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 af631f644249..a5e78a44429e 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 @@ public void OnHeadersComplete() protected override void ClearFast() { - if (!_reuseHeaderValues) + if (!ReuseHeaderValues) { // If we aren't reusing headers clear them all Clear(_bits); @@ -71,7 +72,7 @@ private static long ParseContentLength(string value) } [MethodImpl(MethodImplOptions.NoInlining)] - private void AppendContentLength(Span value) + private void AppendContentLength(ReadOnlySpan value) { if (_contentLength.HasValue) { @@ -82,7 +83,7 @@ private void AppendContentLength(Span value) 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 @@ private bool AddValueUnknown(string key, StringValues value) } [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 @@ public partial struct Enumerator : IEnumerator _current; + private KnownHeaderType _currentKnownType; private readonly bool _hasUnknown; private Dictionary.Enumerator _unknownEnumerator; @@ -135,6 +137,7 @@ internal Enumerator(HttpRequestHeaders collection) _bits = collection._bits; _next = 0; _current = default; + _currentKnownType = default; _hasUnknown = collection.MaybeUnknown != null; _unknownEnumerator = _hasUnknown ? collection.MaybeUnknown.GetEnumerator() @@ -143,6 +146,8 @@ internal Enumerator(HttpRequestHeaders collection) 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 4d5513293e66..a1b30fb94075 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 @@ public override Task FlushAsync(CancellationToken cancellationToken) 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 @@ private ValueTask ReadAsyncWrapper(Memory destination, CancellationTo } } - private async ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken) + private async ValueTask ReadAsyncInternal(Memory destination, CancellationToken cancellationToken) { while (true) { @@ -150,19 +122,19 @@ private async ValueTask ReadAsyncInternal(Memory buffer, Cancellation 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 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio 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 0af675600f88..e2ae921259e4 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 @@ static void CopyExtraHeaders(ref BufferWriter buffer, Dictionary _current; + private KnownHeaderType _currentKnownType; private readonly bool _hasUnknown; private Dictionary.Enumerator _unknownEnumerator; @@ -102,6 +104,7 @@ internal Enumerator(HttpResponseHeaders collection) _bits = collection._bits; _next = 0; _current = default; + _currentKnownType = default; _hasUnknown = collection.MaybeUnknown != null; _unknownEnumerator = _hasUnknown ? collection.MaybeUnknown.GetEnumerator() @@ -110,6 +113,8 @@ internal Enumerator(HttpResponseHeaders collection) 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 2bf5017278b1..86ddb9b157c3 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 @@ public override void Write(byte[] buffer, int offset, int count) 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 ec4144cc8319..6bae47c69088 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 @@ public partial struct Enumerator : IEnumerator _current; + private KnownHeaderType _currentKnownType; private readonly bool _hasUnknown; private Dictionary.Enumerator _unknownEnumerator; @@ -52,6 +53,7 @@ internal Enumerator(HttpResponseTrailers collection) _bits = collection._bits; _next = 0; _current = default; + _currentKnownType = default; _hasUnknown = collection.MaybeUnknown != null; _unknownEnumerator = _hasUnknown ? collection.MaybeUnknown.GetEnumerator() @@ -60,6 +62,8 @@ internal Enumerator(HttpResponseTrailers collection) 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 832a1c56165f..e6e2a0dabf0a 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 @@ public enum HttpVersion 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 18837ccd0a7f..20688fe29199 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 bae364ca566e..e97712f0ae86 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.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 @@ public virtual Task StopAsync() 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 @@ protected void TryStart() 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 @@ protected void TryStop() 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 fce21b621062..23dc6c67c6b2 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 000000000000..c765b22824e6 --- /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 7231f882b73d..3ee7ca584805 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 @@ public InputFlowControl(uint initialWindowSize, uint minWindowSizeIncrement) 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 609398d3d709..198aacb46b23 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 @@ public bool TryUpdateWindow(int bytes) { 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 @@ public void Abort() // 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 c691a22ed776..000000000000 --- 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 afcd4219dc73..7e7c8434e8b4 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 @@ internal class StreamInputFlowControl 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 @@ public StreamInputFlowControl( { _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 @@ public void UpdateWindows(int bytes) 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 35dccd92fc51..792a1ca3a727 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 @@ internal class StreamOutputFlowControl 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 @@ public int AdvanceUpToAndWait(long bytes, out OutputFlowControlAwaitable awaitab _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 @@ public void Abort() // 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 543ce8afc14a..000000000000 --- 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 d549554ab62c..000000000000 --- 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 9268061b2854..000000000000 --- 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 d6a07e7b35d3..000000000000 --- 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 081fd30f6c49..000000000000 --- 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 600d032176ab..000000000000 --- 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 5c0ece5c9f1a..000000000000 --- 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 e00afa1d2824..000000000000 --- 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 000000000000..1598a18c7f67 --- /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 000000000000..290fea38f5ab --- /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 3ccdccef690e..8666bebc64a4 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.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 @@ internal class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeadersHandle 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 @@ internal class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeadersHandle private int _highestOpenedStreamId; private bool _gracefulCloseStarted; - private readonly Dictionary _streams = new Dictionary(); private int _clientActiveStreamCount = 0; private int _serverActiveStreamCount = 0; @@ -76,6 +64,13 @@ internal class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeadersHandle 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 @@ public Http2Connection(HttpConnectionContext context) _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 @@ public async Task ProcessRequestsAsync(IHttpApplication appl 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 @@ public async Task ProcessRequestsAsync(IHttpApplication appl } finally { - Input.AdvanceTo(consumed, examined); + Input.AdvanceTo(buffer.Start, buffer.End); UpdateConnectionState(); } @@ -315,6 +304,11 @@ public async Task ProcessRequestsAsync(IHttpApplication appl 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 @@ private Task ProcessHeadersFrameAsync(IHttpApplication appli } // 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.Reset(); + _currentHeadersStream = GetStream(application); + _headerFlags = _incomingFrame.HeadersFlags; var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding @@ -599,6 +576,52 @@ private Task ProcessHeadersFrameAsync(IHttpApplication appli } } + 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 @@ private Task DecodeHeadersAsync(bool endHeaders, in ReadOnlySequence paylo } catch (Http2StreamErrorException) { + _currentHeadersStream.Dispose(); ResetRequestHeaderParsingState(); throw; } @@ -913,41 +937,62 @@ private Task DecodeTrailersAsync(bool endHeaders, in ReadOnlySequence payl 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); - } + // 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++; - if (_clientActiveStreamCount >= _serverSettings.MaxConcurrentStreams) + try { - throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMaxStreams, Http2ErrorCode.REFUSED_STREAM); - } + // 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; - // 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. + // 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(); + } - // 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) + 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) { - _currentHeadersStream.OnEndStreamReceived(); + 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; } - _streams[_incomingFrame.StreamId] = _currentHeadersStream; - IncrementActiveClientStreamCount(); - _serverActiveStreamCount++; // Must not allow app code to block the connection handling loop. ThreadPool.UnsafeQueueUserWorkItem(_currentHeadersStream, preferLocal: false); } @@ -1030,7 +1075,7 @@ private void UpdateCompletedStreams() throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED); } - _streams.Remove(stream.StreamId); + RemoveStream(stream); } else { @@ -1044,6 +1089,35 @@ private void UpdateCompletedStreams() } } + 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 @@ private void UpdateConnectionState() // 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 @@ public void OnHeader(Span name, Span value) } } - 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 @@ implementations to these vulnerabilities.*/ if (headerField == PseudoHeaderFields.Method) { - _isMethodConnect = value.SequenceEqual(_connectBytes); + _isMethodConnect = value.SequenceEqual(ConnectBytes); } _parsedPseudoHeaderFields |= headerField; @@ -1217,7 +1291,7 @@ implementations to these vulnerabilities.*/ } } - private bool IsPseudoHeaderField(Span name, out PseudoHeaderFields headerField) + private bool IsPseudoHeaderField(ReadOnlySpan name, out PseudoHeaderFields headerField) { headerField = PseudoHeaderFields.None; @@ -1226,23 +1300,23 @@ private bool IsPseudoHeaderField(Span name, out PseudoHeaderFields headerF 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 @@ private bool IsPseudoHeaderField(Span name, out PseudoHeaderFields headerF 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 @@ private async Task ReadInputAsync() } } + 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 7555b9f22389..fd8d5530678c 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.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 @@ public ValueTask Write100ContinueAsync(int streamId) | 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 @@ public void WriteResponseHeaders(int streamId, int statusCode, Http2HeadersFrame 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 @@ public ValueTask WriteResponseTrailers(int streamId, HttpResponseTr 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 @@ private void FinishWritingHeaders(int streamId, int payloadLength, bool done) { _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 @@ private void FinishWritingHeaders(int streamId, int payloadLength, bool done) } } - 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 @@ public ValueTask WriteDataAsync(int streamId, StreamOutputFlowContr // 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 @@ private async ValueTask WriteDataAsync(int streamId, StreamOutputFl while (dataLength > 0) { - OutputFlowControlAwaitable availabilityAwaitable; + ValueTask availabilityTask; var writeTask = default(ValueTask); lock (_writeLock) @@ -365,7 +375,7 @@ private async ValueTask WriteDataAsync(int streamId, StreamOutputFl break; } - var actual = flowControl.AdvanceUpToAndWait(dataLength, out availabilityAwaitable); + var actual = flowControl.AdvanceUpToAndWait(dataLength, out availabilityTask); if (actual > 0) { @@ -395,7 +405,7 @@ private async ValueTask WriteDataAsync(int streamId, StreamOutputFl } // 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 @@ private async ValueTask WriteDataAsync(int streamId, StreamOutputFl // 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 @@ public void AbortPendingStreamDataWrites(StreamOutputFlowControl flowControl) 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 000000000000..421650b9fde2 --- /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 7dad03647508..dd62d69cbb7b 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 @@ internal sealed class Http2MessageBody : MessageBody private readonly Http2Stream _context; private ReadResult _readResult; - private Http2MessageBody(Http2Stream context) + public Http2MessageBody(Http2Stream context) : base(context) { _context = context; @@ -46,14 +46,10 @@ protected override void OnDataRead(long bytesRead) 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 18adcc1a8251..c1c8a320f5a6 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.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 @@ 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,84 @@ internal class Http2OutputProducer : IHttpOutputProducer, IHttpOutputAborter 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; + + /// The core logic for the IValueTaskSource implementation. + private ManualResetValueTaskSourceCore _responseCompleteTaskSource = new ManualResetValueTaskSourceCore { RunContinuationsAsynchronously = true }; // mutable struct, do not make this readonly - public Http2OutputProducer( - int streamId, - Http2FrameWriter frameWriter, - StreamOutputFlowControl flowControl, - MemoryPool pool, - Http2Stream stream, - IKestrelTrace log) + // 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; + _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 +139,9 @@ public ValueTask FlushAsync(CancellationToken cancellationToken) lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); - if (_completed) + if (_streamCompleted) { return default; } @@ -133,14 +165,14 @@ public ValueTask Write100ContinueAsync() { lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); - if (_completed) + if (_streamCompleted) { return default; } - return _frameWriter.Write100ContinueAsync(_streamId); + return _frameWriter.Write100ContinueAsync(StreamId); } } @@ -150,7 +182,7 @@ public void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpRespon { // 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 +207,7 @@ public void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpRespon http2HeadersFrame = Http2HeadersFrameFlags.NONE; } - _frameWriter.WriteResponseHeaders(_streamId, statusCode, http2HeadersFrame, responseHeaders); + _frameWriter.WriteResponseHeaders(StreamId, statusCode, http2HeadersFrame, responseHeaders); } } @@ -188,11 +220,11 @@ public Task WriteDataAsync(ReadOnlySpan data, CancellationToken cancellati 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 +240,16 @@ public ValueTask WriteStreamSuffixAsync() { lock (_dataWriterLock) { - if (_completed) + if (_streamCompleted) { - return _dataWriteProcessingTask; + return GetWaiterTask(); } - _completed = true; + _streamCompleted = true; _suffixSent = true; _pipeWriter.Complete(); - return _dataWriteProcessingTask; + return GetWaiterTask(); } } @@ -228,7 +260,7 @@ public ValueTask WriteRstStreamAsync(Http2ErrorCode error) // 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 +268,9 @@ public void Advance(int bytes) { lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); - if (_completed) + if (_streamCompleted) { return; } @@ -253,9 +285,9 @@ public Span GetSpan(int sizeHint = 0) { lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); - if (_completed) + if (_streamCompleted) { return GetFakeMemory(sizeHint).Span; } @@ -268,9 +300,9 @@ public Memory GetMemory(int sizeHint = 0) { lock (_dataWriterLock) { - ThrowIfSuffixSentOrDisposed(); + ThrowIfSuffixSentOrCompleted(); - if (_completed) + if (_streamCompleted) { return GetFakeMemory(sizeHint); } @@ -283,7 +315,7 @@ public void CancelPendingFlush() { lock (_dataWriterLock) { - if (_completed) + if (_streamCompleted) { return; } @@ -301,11 +333,11 @@ public ValueTask WriteDataToPipeAsync(ReadOnlySpan data, Canc 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 +373,12 @@ public void Stop() { lock (_dataWriterLock) { - if (_completed) + if (_streamCompleted) { return; } - _completed = true; + _streamCompleted = true; _pipeReader.CancelPendingRead(); @@ -358,66 +390,90 @@ public void Reset() { } - 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) - { - // Response body is aborted, break and complete reader. - break; - } - else if (readResult.IsCompleted && _stream.ResponseTrailers?.Count > 0) + do { - // 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); + } + + _stream.ResponseTrailers.SetReadOnly(); + _stream.DecrementActiveClientStreamCount(); + flushResult = await _frameWriter.WriteResponseTrailers(StreamId, _stream.ResponseTrailers); } + else if (readResult.IsCompleted && _streamEnded) + { + if (readResult.Buffer.Length != 0) + { + ThrowUnexpectedState(); + } - // 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) + // Headers have already been written and there is no other content to write + flushResult = await _frameWriter.FlushAsync(outputAborter: null, cancellationToken: default); + } + else { - _stream.DecrementActiveClientStreamCount(); + var endStream = readResult.IsCompleted; + if (endStream) + { + _stream.DecrementActiveClientStreamCount(); + } + flushResult = await _frameWriter.WriteDataAsync(StreamId, _flowControl, readResult.Buffer, endStream, forceFlush: true); } - flushResult = await _frameWriter.WriteDataAsync(_streamId, _flowControl, readResult.Buffer, endStream); - } - _pipeReader.AdvanceTo(readResult.Buffer.End); - } while (!readResult.IsCompleted); - } - catch (Exception ex) - { - _log.LogCritical(ex, nameof(Http2OutputProducer) + "." + nameof(ProcessDataWrites) + " observed an unexpected exception."); - } + _pipeReader.AdvanceTo(readResult.Buffer.End); + } while (!readResult.IsCompleted); + } + catch (Exception ex) + { + _log.LogCritical(ex, nameof(Http2OutputProducer) + "." + nameof(ProcessDataWrites) + " observed an unexpected exception."); + } - _pipeReader.Complete(); + _pipeReader.Complete(); - return flushResult; + // Signal via WriteStreamSuffixAsync to the stream that output has finished. + // Stream state will move to RequestProcessingStatus.ResponseCompleted + _responseCompleteTaskSource.SetResult(flushResult); + + if (readResult.IsCompleted) + { + // Successfully read all data. Wait here for the stream to be reset. + await new ValueTask(_resetAwaitable, _resetAwaitable.Version); + _resetAwaitable.Reset(); + } + else + { + // Stream was aborted. + break; + } + } while (!_disposed); static void ThrowUnexpectedState() { @@ -436,16 +492,16 @@ private Memory GetFakeMemory(int sizeHint) } [StackTraceHidden] - private void ThrowIfSuffixSentOrDisposed() + private void ThrowIfSuffixSentOrCompleted() { if (_suffixSent) { ThrowSuffixSent(); } - if (_disposed) + if (_writerComplete) { - ThrowDisposed(); + ThrowWriterComplete(); } } @@ -456,7 +512,7 @@ private static void ThrowSuffixSent() } [StackTraceHidden] - private static void ThrowDisposed() + private static void ThrowWriterComplete() { throw new InvalidOperationException("Cannot write to response after the request has completed."); } @@ -472,5 +528,16 @@ private static Pipe CreateDataPipe(MemoryPool pool) useSynchronizationContext: false, minimumSegmentSize: pool.GetMinimumSegmentSize() )); + + public void Dispose() + { + if (_disposed) + { + return; + } + _disposed = true; + + _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 27dd7762ae63..67c1e993d251 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 @@ 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); - - _outputFlowControl = new StreamOutputFlowControl( - context.ConnectionOutputFlowControl, - context.ClientPeerSettings.InitialWindowSize); - - _http2Output = new Http2OutputProducer( - context.StreamId, - context.FrameWriter, - _outputFlowControl, - context.MemoryPool, - this, - context.ServiceContext.Log); - - RequestBodyPipe = CreateRequestBodyPipe(context.ServerPeerSettings.InitialWindowSize); - Output = _http2Output; + // 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); + + _http2Output = new Http2OutputProducer(this, context, _outputFlowControl); + + 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 @@ public bool ReceivedEmptyRequestBody } } + 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 @@ protected override void OnRequestProcessingEnded() // 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,10 @@ protected override void OnRequestProcessingEnded() // connection's flow-control window. _inputFlowControl.Abort(); - Reset(); + // We only want to reuse a stream that has completely finished writing. + // This is to prevent the situation where Http2OutputProducer.ProcessDataWrites + // is still running in the background. + CanReuse = !_keepAlive && HasResponseCompleted; } finally { @@ -125,7 +171,23 @@ protected override string CreateRequestId() => 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 +218,7 @@ private bool TryValidatePseudoHeaders() // 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 +237,16 @@ private bool TryValidatePseudoHeaders() // - 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 +283,7 @@ private bool TryValidatePseudoHeaders() private bool TryValidateMethod() { // :method - _methodText = RequestHeaders[HeaderNames.Method].ToString(); + _methodText = HttpRequestHeaders.HeaderMethod.ToString(); Method = HttpUtilities.GetKnownMethod(_methodText); if (Method == HttpMethod.None) @@ -247,7 +309,7 @@ private bool TryValidateAuthorityAndHost(out string hostText) // :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 +362,18 @@ private bool TryValidatePath(ReadOnlySpan pathSegment) 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 +575,7 @@ public void DecrementActiveClientStreamCount() _context.StreamLifetimeHandler.DecrementActiveClientStreamCount(); } - private Pipe CreateRequestBodyPipe(uint windowSize) + private Pipe CreateRequestBodyPipe() => new Pipe(new PipeOptions ( pool: _context.MemoryPool, @@ -512,8 +583,8 @@ private Pipe CreateRequestBodyPipe(uint windowSize) 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 +605,11 @@ private Pipe CreateRequestBodyPipe(uint windowSize) /// 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 e6026189765b..2ba223f43cbc 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 @@ internal sealed class Http2Stream : Http2Stream, IHostContextContainer { 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 000000000000..1c141dd236fa --- /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 000000000000..b96554c171c8 --- /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 fc0417304679..b852ed8b5ba1 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 000000000000..53bdc4d4bd1e --- /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 000000000000..9913c010bd24 --- /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 000000000000..a90902470d3f --- /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 000000000000..f174f4b3266a --- /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 000000000000..3aa7d990a310 --- /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 000000000000..c410c34a3f9b --- /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 000000000000..a75d8bc69ad6 --- /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 000000000000..880f0f4c1b55 --- /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 000000000000..98ce27e8716a --- /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 000000000000..bb2e33a4cbbb --- /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 000000000000..5af5f8df479a --- /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 000000000000..6c2f142591c9 --- /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 0ed16148b88c..4a2961d7ecbe 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 f20769f0b63d..325ac0fd91bc 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 000000000000..6518d00afd86 --- /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 000000000000..8edccac290bb --- /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 000000000000..b0dc9e472926 --- /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 000000000000..6b53b7990060 --- /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 000000000000..63388ffdfb92 --- /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 000000000000..54ebad55dbf2 --- /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 000000000000..e20f8c33a6f5 --- /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 000000000000..9107b6dc8406 --- /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 de8411d7f8c6..a00f6002a485 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.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 @@ public async Task ProcessRequestsAsync(IHttpApplication http 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 562b7bd1a965..69c46ec79c87 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 b5dc202e01bd..dd31fde12fb2 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 7cba84b13884..9cf9e2d62e35 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 @@ internal class Heartbeat : IDisposable 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 @@ internal void OnHeartbeat() if (Interlocked.Exchange(ref _executingOnHeartbeat, 1) == 0) { + Volatile.Write(ref _lastHeartbeatTicks, now.Ticks); + try { foreach (var callback in _callbacks) @@ -66,7 +69,9 @@ internal void OnHeartbeat() { 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 04fd9711d711..70fa7f00b370 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 @@ internal static partial class HttpUtilities 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 @@ private static unsafe ulong GetMaskAsLong(byte[] bytes) } // 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 @@ private static unsafe string GetAsciiOrUTF8StringNonNullCharacters(this Span 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 Span span) + private static unsafe string GetLatin1StringNonNullCharacters(this ReadOnlySpan span) { if (span.IsEmpty) { @@ -189,7 +218,7 @@ private static unsafe string GetLatin1StringNonNullCharacters(this Span sp 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 @@ public static HttpMethod GetKnownMethod(string value) { 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 @@ public static HttpMethod GetKnownMethod(string value) { 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 @@ private static unsafe bool GetKnownHttpScheme(byte* location, int length, out Ht public static string VersionToString(HttpVersion httpVersion) { - return httpVersion switch - { - HttpVersion.Http10 => Http10Version, - HttpVersion.Http11 => Http11Version, - _ => null, + switch (httpVersion) + { + 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 1a5815300e24..cd441ae3a284 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 @@ internal interface IKestrelTrace : ILogger 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 bec07b018d11..5365ed739790 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 @@ 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 @@ internal class KestrelConnection : IConnectionHeartbeatFeature, IConnectionCompl 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 @@ public void TickHeartbeat() } } + public abstract BaseConnectionContext TransportConnection { get; } + public void OnHeartbeat(Action action, object state) { lock (_heartbeatLock) @@ -124,7 +117,7 @@ private Task CompleteAsyncMayAwait(Stack, object } 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 @@ private async Task CompleteAsyncAwaited(Task currentTask, Stack : 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 fdabf48247f6..19afb9af715c 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 @@ private KestrelEventSource() // - 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 @@ private void ConnectionStart(string connectionId, } [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 d72aa3c707dc..f89732a7044c 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 @@ internal class KestrelTrace : IKestrelTrace 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 @@ public virtual void NotAllConnectionsAborted() _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 @@ public virtual void HPackEncodingError(string connectionId, int streamId, HPackE 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 fdd8ac7367d5..a9bf8d942404 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 @@ public void SetOwnedMemory(byte[] arrayPoolBuffer) 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 3ab051c8c413..5e871e9dadbd 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 @@ public ConcurrentPipeWriter(PipeWriter innerPipeWriter, MemoryPool pool, o _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 @@ private BufferSegment AllocateSegmentUnsynchronized(int sizeHint) } 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 000000000000..e0fe1edbdc72 --- /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 6eb5ada99de4..fdd2b47319cf 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -5,6 +5,7 @@ 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(IEnumerable transportFactories, ServiceContext serviceContext) + : this(transportFactories, null, serviceContext) { } // For testing - internal KestrelServer(IConnectionListenerFactory transportFactory, ServiceContext serviceContext) + internal KestrelServer(IEnumerable transportFactories, IEnumerable multiplexedFactories, ServiceContext serviceContext) { - if (transportFactory == null) + 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 @@ private static ServiceContext CreateServiceContext(IOptions(IHttpApplication application, C async Task OnBind(ListenOptions options) { - // Add the HTTP middleware as the terminal connection middleware - options.UseHttpServer(ServiceContext, application, options.Protocols); + // 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) + { + if (_multiplexedTransportFactories == null || _multiplexedTransportFactories.Count == 0) + { + throw new InvalidOperationException("Cannot start HTTP/3 server if no MultiplexedTransportFactories are registered."); + } - var connectionDelegate = options.Build(); + options.UseHttp3Server(ServiceContext, application, options.Protocols); + var multiplxedConnectionDelegate = ((IMultiplexedConnectionBuilder)options).Build(); - // Add the connection limit middleware - if (Options.Limits.MaxConcurrentConnections.HasValue) - { - connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync; + 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(); + + // Add the connection limit middleware + if (Options.Limits.MaxConcurrentConnections.HasValue) + { + connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync; + } - // Update the endpoint - options.EndPoint = transport.EndPoint; - var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(transport); + var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); + var factory = _transportFactories.Last(); + var transport = await factory.BindAsync(options.EndPoint).ConfigureAwait(false); - _transports.Add((transport, acceptLoopTask)); + 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 @@ public async Task StopAsync(CancellationToken cancellationToken) 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 @@ public async Task StopAsync(CancellationToken cancellationToken) } } - 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 10db0fe66289..46a916e9630a 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 @@ public long? MaxConcurrentUpgradedConnections /// 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 35a5b0bd49a9..71def30d7399 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -72,6 +72,11 @@ public class KestrelServerOptions /// 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 14e483c403dd..16a8e0547775 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 @@ public IConnectionBuilder Use(Func middl return this; } + IMultiplexedConnectionBuilder IMultiplexedConnectionBuilder.Use(Func middleware) + { + _multiplexedMiddleware.Add(middleware); + return this; + } + public ConnectionDelegate Build() { ConnectionDelegate app = context => @@ -139,6 +146,22 @@ public ConnectionDelegate Build() 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 6c18f1a07820..72a38463da28 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 000000000000..c330a69ca660 --- /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 e46a2c2a831e..09c1ce476cfa 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 @@ public static IConnectionBuilder UseHttpServer(this IConnectionBuilder 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 c3f24b4afd97..a568f7a02c5b 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs @@ -77,16 +77,20 @@ public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapter } _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 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) 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 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) _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 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) !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 @@ private static X509Certificate2 ConvertToX509Certificate2(X509Certificate certif 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 e3fdec3f8180..15758f64f455 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 @@ private void Log(string method, ReadOnlySpan buffer) // 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 b5305f5c1d01..d4aaee32b78f 100644 --- a/src/Servers/Kestrel/Core/test/AddressBinderTests.cs +++ b/src/Servers/Kestrel/Core/test/AddressBinderTests.cs @@ -77,7 +77,7 @@ public void ParseAddressLocalhost() } [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 @@ public void ParseAddressUnixPipe() 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 1315c24b4b7a..33bba9c48ea1 100644 --- a/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs +++ b/src/Servers/Kestrel/Core/test/ConnectionDispatcherTests.cs @@ -29,7 +29,7 @@ public async Task OnConnectionCreatesLogScopeWithConnectionId() 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 @@ public async Task OnConnectionFiresOnCompleted() 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 @@ public async Task OnConnectionOnCompletedExceptionCaught() 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 @@ public async Task OnConnectionOnCompletedExceptionCaught() 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 a7fb8520c653..000000000000 --- 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 57ee0ba9a480..3b290d712be6 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 @@ public class HPackEncoderTests [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 @@ public void EncodesHeadersInSinglePayloadWhenSpaceAvailable(KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"), new KeyValuePair("content-type", "text/html; charset=utf-8"), @@ -156,33 +155,45 @@ public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize 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 4448463dd40e..000000000000 --- 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 f0d4485b3ee8..bb4e896b44b7 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 @@ public async Task BlockedHeartbeatDoesntCauseOverlapsAndIsLoggedAsError() 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 @@ public async Task BlockedHeartbeatIsNotLoggedAsErrorIfDebuggerAttached() 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 f602d0979878..0992626a80f3 100644 --- a/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs @@ -69,7 +69,6 @@ public Http1ConnectionTests() }; _http1Connection = new TestHttp1Connection(_http1ConnectionContext); - _http1Connection.Reset(); } public void Dispose() @@ -751,7 +750,7 @@ public void RequestAbortedTokenIsUsableAfterCancellation() _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 000000000000..8aed0c4498b4 --- /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 815fe5019a4e..42867902ff0a 100644 --- a/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpConnectionManagerTests.cs @@ -44,7 +44,7 @@ private void UnrootedConnectionsGetRemovedFromHeartbeatInnerScope( 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 7ce8587743dc..1b2646d16bb8 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.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 @@ public void ParseRequestLineSplitBufferWithoutNewLineDoesNotUpdateConsumed() 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 @@ private class RequestHandler : IHttpRequestLineHandler, IHttpHeadersHandler 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 @@ public void OnStartLine(HttpMethod method, HttpVersion version, Span targe 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 030931dae46f..f62bad9117e8 100644 --- a/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs @@ -249,8 +249,9 @@ private int SetFeaturesToNonDefault() 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 ed3c492e3cc5..1bbe06e99c76 100644 --- a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs @@ -138,6 +138,15 @@ public void ThrowsWhenAddingHeaderAfterReadOnlyIsSet() 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 9b58a1e878a2..e8778dc3903d 100644 --- a/src/Servers/Kestrel/Core/test/HttpUtilitiesTest.cs +++ b/src/Servers/Kestrel/Core/test/HttpUtilitiesTest.cs @@ -52,8 +52,8 @@ public void GetsKnownMethod(string input, bool expectedResult, string expectedKn } [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 fdebb569229f..000000000000 --- 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 c667cc6cee2d..000000000000 --- 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 36add68a21a1..56454bb7a41a 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 @@ public void LoggerCategoryNameIsKestrelServerNamespace() 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("transportFactories", exception.ParamName); + } - Assert.Equal("transportFactory", 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 @@ public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() 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 @@ public async Task StopAsyncCallsCompleteWithThrownException() 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 @@ public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() 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 @@ public void StartingServerInitializesHeartbeat() 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 @@ public void StartingServerInitializesHeartbeat() 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 @@ public ValueTask BindAsync(EndPoint endpoint, CancellationT 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 7461bb8eab6f..b44caa62d468 100644 --- a/src/Servers/Kestrel/Core/test/MessageBodyTests.cs +++ b/src/Servers/Kestrel/Core/test/MessageBodyTests.cs @@ -810,6 +810,9 @@ public async Task LogsWhenStartsReadingRequestBody() 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 @@ public async Task LogsWhenStopsReadingRequestBody() 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 0497c913429f..108edc514d13 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 ebf57aebeb4a..d95b5c18db69 100644 --- a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs +++ b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs @@ -87,7 +87,7 @@ public void EncodesAsAscii(string input, byte[] expected) { 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 @@ public void EncodesAsAscii(string input, byte[] expected) [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 @@ public void WriteAsciiNoValidationWritesOnlyOneBytePerChar(string input) } [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 @@ public void WritesAsciiAcrossBlockBoundaries(int stringLength, int gapSize) 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 170d7b1479be..9477729e3a40 100644 --- a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs +++ b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs @@ -328,7 +328,7 @@ public void ReadTimingNotEnforcedWhenLowConnectionInputFlowControlAvailability() // 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 4c4e37740047..35043d8322dc 100644 --- a/src/Servers/Kestrel/Core/test/UTF8Decoding.cs +++ b/src/Servers/Kestrel/Core/test/UTF8Decoding.cs @@ -17,7 +17,7 @@ public class UTF8DecodingTests [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 @@ private void ExceptionThrownForZeroOrNonAscii(byte[] bytes) 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 000000000000..579d85a68771 --- /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 5f7b931878de..39711faec1b9 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 67969b30f596..da53ab050beb 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 @@ public void GeneratedCodeIsUpToDate() 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 23fb6115518e..a8035946ba3d 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs @@ -218,7 +218,7 @@ public void ConfigureEndpointDevelopmentCertificateGetsLoadedWhenPresent() 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 @@ public void ConfigureEndpointDevelopmentCertificateGetsIgnoredIfPasswordIsNotCor 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 @@ public void ConfigureEndpointDevelopmentCertificateGetsIgnoredIfPfxFileDoesNotEx // [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 @@ private void DefaultConfigSectionCanSetProtocols(string input, HttpProtocols exp // [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 cade793bc20a..abf2fb6b3a1a 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 000000000000..cbdc0002cb14 --- /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 0e07f1a69c5b..1e5b7bb75b61 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 9dc11a31a440..62797641018c 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 @@ public Task StartAsync( 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 acbc356294d3..070ee5a73b3b 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 @@ private void PostCallback() 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 @@ public void ReadCallback(UvStreamHandle dispatchPipe, int status) { // 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 06c8aec79669..0aa477f3af98 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs @@ -27,6 +27,14 @@ public class LibuvTransportOptions /// 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 3fc89b9e9f4e..9bc5d6b037ce 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 03c24d661aac..77fa96d0081c 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvOutputConsumerTests.cs @@ -606,7 +606,7 @@ await Task.Run(async () => 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 13595d51ae7c..db7ad6033aa8 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -184,7 +184,7 @@ public async Task OneToTenThreads(int threadCount) 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 000000000000..1c0f135cdbe0 --- /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 000000000000..6165d46de875 --- /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 000000000000..d91db88d7028 --- /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 000000000000..75bdb998310d --- /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 000000000000..c6d50f0250ab --- /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 000000000000..8a17ae6aa531 --- /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 000000000000..85f8119490a3 --- /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 000000000000..0ca29b2d2dda --- /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 000000000000..82340cf94154 --- /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 000000000000..69b85b72046e --- /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 000000000000..abc73c72c0d2 --- /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 000000000000..98b089c892c3 --- /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 000000000000..801ef8da8ff2 --- /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 000000000000..2c5ab8be3732 --- /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 c1e32cc39d9f..020e80124d33 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 @@ public static partial class WebHostBuilderSocketExtensions } 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 @@ public SocketTransportFactory(Microsoft.Extensions.Options.IOptions _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 9c8f2aef9d72..0d59cd1c4364 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 @@ 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 @@ internal class SocketAwaitableEventArgs : SocketAsyncEventArgs, ICriticalNotifyC 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 807473945403..4088dab97871 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 @@ internal sealed class SocketConnection : TransportConnection 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 @@ internal SocketConnection(Socket socket, _socket = socket; MemoryPool = memoryPool; _trace = trace; + _waitForData = waitForData; LocalEndPoint = _socket.LocalEndPoint; RemoteEndPoint = _socket.RemoteEndPoint; @@ -186,8 +189,11 @@ private async Task ProcessReceives() 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 ecdca4391bd0..278fed94dd95 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 ccdb7746748e..4c49a9023333 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -62,6 +62,13 @@ internal void Bind() 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 @@ internal void Bind() EndPoint = listenSocket.LocalEndPoint; - listenSocket.Listen(512); + listenSocket.Listen(_options.Backlog); _listenSocket = listenSocket; } @@ -105,7 +112,8 @@ public async ValueTask AcceptAsync(CancellationToken cancella 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 4adb16ebfb3e..957876ca5953 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs @@ -16,6 +16,14 @@ public class SocketTransportOptions /// 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 @@ public class SocketTransportOptions /// 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 52b26c66bc15..5f1475a1cfcd 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 d073f91aa45d..8ee49f198d29 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs @@ -8,6 +8,9 @@ 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 887aad393996..d54f4a879f13 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 @@ public Adapter(Http1ConnectionBenchmark requestHandler) 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 000000000000..b3e8f15403ce --- /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 000000000000..839558d1a3ad --- /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 c5eb24baf89f..3960ebe38802 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 @@ public void OnStartLine(HttpMethod method, HttpVersion version, Span targe { } - 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 @@ public Adapter(HttpParserBenchmark requestHandler) 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 6fc22390c0c3..e41d076ec69a 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 61d180a73c47..ef22ae3002d7 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 000000000000..86f6dc311210 --- /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 000000000000..e89076aba26a --- /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 000000000000..960d2cba950d --- /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 000000000000..54865db3c3d3 --- /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 d2514f998f39..bd93636c83fb 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 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except 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 288588f3b11e..53bae7b1b5fe 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 @@ public bool ParseHeaders(TRequestHandler handler, ref SequenceReader reade 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 000000000000..c982ec490292 --- /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 2b16c54e42ba..000000000000 --- 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 969272d54862..000000000000 --- 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 d8e539f51dd3..000000000000 --- 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 37a45db13b22..000000000000 --- 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 9551661831a0..000000000000 --- 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 c7d0893969ec..000000000000 --- 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 47415305b2fa..000000000000 --- 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 2e3fb2fa7134..000000000000 --- 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 48c736ab275e..000000000000 --- 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 2d58819ae2e8..000000000000 --- 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 298193b81229..000000000000 --- 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 5ac7ed26d298..000000000000 --- 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 784af36c2bcb..000000000000 --- 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 8313f994bd00..000000000000 --- 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 6cfe040ea40e..000000000000 --- 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 c6b37eb216b0..2b26fe09c608 100644 --- a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs @@ -28,13 +28,13 @@ public static void Main(string[] args) 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 @@ public static void Main(string[] args) // 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/Http3SampleApp/Http3SampleApp.csproj b/src/Servers/Kestrel/samples/Http3SampleApp/Http3SampleApp.csproj new file mode 100644 index 000000000000..04b7e7420cda --- /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 000000000000..7e37138aaf82 --- /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 000000000000..0434605cfca5 --- /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 000000000000..e203e9407e74 --- /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 000000000000..d9d9a9bff6fd --- /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 000000000000..54a11c63a054 --- /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 000000000000..56ee0e01e14d --- /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 000000000000..e203e9407e74 --- /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 000000000000..d9d9a9bff6fd --- /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 000000000000..5ced2cc44bd8 --- /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 000000000000..902824d8367d --- /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 000000000000..f7a8634bda53 --- /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 000000000000..5bfcc5fda77d --- /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 b2f3292f3415..e6389a675eb5 100644 --- a/src/Servers/Kestrel/shared/KnownHeaders.cs +++ b/src/Servers/Kestrel/shared/KnownHeaders.cs @@ -19,6 +19,10 @@ static KnownHeaders() { var requestPrimaryHeaders = new[] { + ":authority", + ":method", + ":path", + ":scheme", "Accept", "Connection", "Host", @@ -72,6 +76,10 @@ static KnownHeaders() }; RequestHeaders = commonHeaders.Concat(new[] { + ":authority", + ":method", + ":path", + ":scheme", "Accept", "Accept-Charset", "Accept-Encoding", @@ -145,6 +153,7 @@ static KnownHeaders() { "Accept-Ranges", "Age", + "Alt-Svc", "ETag", "Location", "Proxy-Authenticate", @@ -246,7 +255,7 @@ public class KnownHeader { 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 @@ public class KnownHeader 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 @@ public static string GeneratedFile() 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 @@ public static string GeneratedFile() 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 @@ internal unsafe void CopyToFast(ref BufferWriter output) 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 @@ public unsafe void Append(Span name, Span value) }} // 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 @@ public unsafe void Append(Span name, Span value) // 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 @@ public bool MoveNext() 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 @@ public bool MoveNext() 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 @@ public bool MoveNext() 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 1b7a21697673..a9ba1cd76284 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 @@ object IFeatureCollection.this[Type key] 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 @@ object IFeatureCollection.this[Type key] { _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 @@ private IEnumerable> FastEnumerable() { 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 000000000000..fae4129d25c5 --- /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 000000000000..e7df1de198e7 --- /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 000000000000..7dea0569b9f7 --- /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 f4b55a18a6eb..85a7643202dc 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 @@ public void NotAllConnectionsAborted() _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 000000000000..481278ae4297 --- /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 e6eeeaa2e1f2..888ccb032a97 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/testCert.pfx b/src/Servers/Kestrel/shared/test/TestCertificates/testCert.pfx index 888ccb032a97..94befa6e7fd4 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 d79a6d905554..7ff959bb1f4a 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 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action(), 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 8bddf0cddb0d..c962d5bfd78d 100644 --- a/src/Servers/Kestrel/stress/Program.cs +++ b/src/Servers/Kestrel/stress/Program.cs @@ -237,7 +237,7 @@ void ValidateContent(string expectedContent, string actualContent) 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 205bdbbe5f9d..787d3fe7c8f0 100644 --- a/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs +++ b/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs @@ -32,7 +32,7 @@ public class AddressRegistrationTests : TestApplicationErrorLoggerLoggedTest [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 @@ public async Task RegisterIPEndPoint_IPv6StaticPort_Success() [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 @@ public async Task RegisterIPEndPoint_DynamicPort_Success(IPEndPoint endPoint, st [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 @@ public async Task RegisterAddresses_IPv6_Success(string addressInput, string[] t [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 @@ public async Task RegisterAddresses_IPv6Port5000Default_Success(string addressIn [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 @@ public async Task RegisterAddresses_IPv6Port80_Success(string addressInput, stri } [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 @@ public async Task ListenAnyIP_IPv6_Success() [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 @@ public async Task OverrideDirectConfigurationWithIServerAddressesFeature_Succeed } [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 @@ private void ThrowsWhenBindingLocalhostToAddressInUse(AddressFamily addressFamil 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 aaf101cced18..7ffd9b2bc0d1 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs @@ -58,7 +58,7 @@ public void TlsAndHttp2NotSupportedOnMac() [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 @@ public void TlsAndHttp2NotSupportedOnWin7() [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 @@ public async Task TlsAlpnHandshakeSelectsHttp2From1and2() [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 af2b768fd1da..9ce2aa056302 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 @@ public ShutdownTests() [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 @@ public async Task GracefulShutdownWaitsForRequestsToFinish() 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 @@ public async Task GracefulTurnsAbortiveIfRequestsDoNotFinish() 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 f1ed973b5b55..4236508cb978 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs @@ -108,7 +108,7 @@ from ssl in sslValues } } [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 @@ public async Task LargeUpload(long? maxRequestBufferSize, bool connectionAdapter } [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 6e4a5b880349..1953b86c6bde 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs @@ -132,9 +132,6 @@ public Task RemoteIPv4Address() [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"); @@ -538,7 +535,6 @@ await connection.Send( } [Theory] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2181", FlakyOn.Helix.All)] [MemberData(nameof(ConnectionMiddlewareData))] public async Task ConnectionClosedTokenFiresOnServerFIN(ListenOptions listenOptions) { @@ -791,9 +787,6 @@ await connection.Send( } [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 d3fa68a1bdbd..05148cbebd19 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -17,6 +17,7 @@ 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 @@ await connection.Send( } [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 @@ await connection.Send( } } - [Fact] + [ConditionalFact] public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseChunks() { var chunkSize = 64 * 128 * 1024; @@ -755,6 +755,9 @@ public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLarg }; testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); @@ -781,16 +784,23 @@ async Task App(HttpContext context) await connection.Send( "GET / HTTP/1.1", "Host:", - "Connection: close", "", ""); - var minTotalOutputSize = chunkCount * chunkSize; + 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; - 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 AssertBytesReceivedAtTargetRate(connection.Stream, expectedBytes: 33_553_537, targetBytesPerSecond); await appFuncCompleted.Task.DefaultTimeout(); + + connection.ShutdownSend(); + await connection.WaitForConnectionClose(); } await server.StopAsync(); } @@ -800,12 +810,9 @@ await connection.Send( 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] + [QuarantinedTest] public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeaders() { var headerSize = 1024 * 1024; // 1 MB for each header value @@ -829,6 +836,9 @@ public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLarg }; testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); @@ -858,6 +868,85 @@ await connection.Send( ""); } + 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)); + + 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", @@ -866,12 +955,18 @@ await connection.Send( "", ""); - var responseSize = headerSize * headerCount; - var minTotalOutputSize = requestCount * responseSize; + await connection.Receive( + "HTTP/1.1 200 OK", + "Connection: close", + $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); - // Make sure consuming a single set of response headers exceeds the 2 second timeout. - var targetBytesPerSecond = responseSize / 4; - await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); + // 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 AssertStreamCompletedAtTargetRate(connection.Stream, expectedBytes: 33_553_556, targetBytesPerSecond); + await appFuncCompleted.Task.DefaultTimeout(); } await server.StopAsync(); } @@ -908,7 +1003,30 @@ private async Task AssertStreamAborted(Stream stream, int totalBytes) 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 @@ private async Task AssertStreamCompleted(Stream stream, long minimumBytes, int t } } 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 12fe59d9e82a..a6a49d8b3bdd 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/UnixDomainSocketsTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/UnixDomainSocketsTests.cs @@ -12,6 +12,8 @@ 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 @@ async Task EchoServer(ConnectionContext connection) 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 @@ async Task EchoServer(ConnectionContext connection) } } +#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 3d109476a581..f279141a7d30 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedResponseTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedResponseTests.cs @@ -78,6 +78,42 @@ await connection.ReceiveEnd( } } + [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 43c92b012192..532629f0ddb9 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionLimitTests.cs @@ -142,7 +142,7 @@ public async Task RejectsConnectionsWhenLimitReached() } [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 a294d65225bb..6fd7ce1d8b49 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.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 @@ public async Task ServerSettings_ChangesRequestMaxFrameSize() 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 @@ public async Task DATA_Received_ReadByStream() 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 @@ public async Task DATA_Received_MaxSize_ReadByStream() 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 @@ public async Task DATA_Received_GreaterThanInitialWindowSize_ReadByStream() } await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -271,7 +785,7 @@ public async Task DATA_Received_Multiple_ReadByStream() 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 @@ public async Task DATA_Received_Multiplexed_ReadByStreams() 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 @@ await ExpectAsync(Http2FrameType.HEADERS, 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 @@ public async Task DATA_Received_Multiplexed_GreaterThanInitialWindowSize_ReadByS } await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -431,7 +945,7 @@ await ExpectAsync(Http2FrameType.HEADERS, withStreamId: 0); await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); @@ -509,7 +1023,7 @@ await InitializeConnectionAsync(async context => 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 @@ await ExpectAsync(Http2FrameType.DATA, 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 @@ public async Task DATA_Received_WithPadding_ReadByStream(byte padLength) 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 @@ public async Task DATA_Received_WithPadding_CountsTowardsInputFlowControl(byte p } await ExpectAsync(Http2FrameType.HEADERS, - withLength: 37, + withLength: 33, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); @@ -655,7 +1169,7 @@ public async Task DATA_Received_ButNotConsumedByApp_CountsTowardsInputFlowContro } await ExpectAsync(Http2FrameType.HEADERS, - withLength: 55, + withLength: 37, withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), withStreamId: 1); @@ -724,7 +1238,7 @@ await InitializeConnectionAsync(async context => 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 @@ public async Task DATA_Received_StreamClosed_ConnectionError() 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 @@ await WaitForConnectionErrorAsync( [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 @@ await InitializeConnectionAsync(async context => 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 @@ await ExpectAsync(Http2FrameType.HEADERS, 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 @@ public async Task DATA_Received_StreamClosedImplicitly_ConnectionError() 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 @@ await InitializeConnectionAsync(async context => 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 @@ await ExpectAsync(Http2FrameType.DATA, 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 @@ public async Task DATA_Sent_DespiteStreamOutputFlowControl_IfEmptyAndEndsStream( 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 @@ public async Task HEADERS_Received_Decoded() 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 @@ public async Task HEADERS_Received_WithPadding_Decoded(byte padLength) 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 @@ public async Task HEADERS_Received_WithPriority_Decoded() 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 @@ public async Task HEADERS_Received_WithPriorityAndPadding_Decoded(byte padLength 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 @@ public async Task HEADERS_Received_WithTrailers_Available(bool sendData) // 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 @@ await ExpectAsync(Http2FrameType.HEADERS, 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 @@ public async Task HEADERS_Received_ContainsExpect100Continue_100ContinueSent() 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 @@ await InitializeConnectionAsync(async context => 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 @@ public async Task HEADERS_OverMaxStreamLimit_Refused() 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 @@ public async Task HEADERS_Received_StreamClosed_ConnectionError() 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 @@ public async Task HEADERS_Received_StreamClosedImplicitly_ConnectionError() 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 @@ await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, - expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock); + expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); } [Fact] @@ -1592,7 +2106,7 @@ await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, - expectedErrorMessage: CoreStrings.HPackErrorIntegerTooBig); + expectedErrorMessage: SR.net_http_hpack_bad_integer); } [Theory] @@ -1687,7 +2201,7 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade 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 @@ public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeade { 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 @@ public async Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_N 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 @@ await InitializeConnectionAsync(async context => 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 @@ await ExpectAsync(Http2FrameType.DATA, // 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 @@ async Task VerifyStreamBackpressure(int streamId) 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 @@ public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize() _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 @@ await InitializeConnectionAsync(context => 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 @@ public async Task GOAWAY_Received_SetsConnectionStateToClosingAndWaitForAllStrea 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 @@ await ExpectAsync(Http2FrameType.DATA, 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 @@ await InitializeConnectionAsync(async context => 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 @@ await ExpectAsync(Http2FrameType.DATA, // 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 @@ async Task VerifyStreamBackpressure(int streamId) 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 @@ await InitializeConnectionAsync(async context => 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 @@ public async Task WINDOW_UPDATE_Received_OnStream_Respected() 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 @@ public async Task WINDOW_UPDATE_Received_OnStream_Respected_WhenInitialWindowSiz 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 @@ public async Task CONTINUATION_Received_Decoded() 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 @@ await ExpectAsync(Http2FrameType.HEADERS, [Theory] [InlineData(true)] [InlineData(false)] + [QuarantinedTest] public async Task CONTINUATION_Received_WithTrailers_Available(bool sendData) { await InitializeConnectionAsync(_readTrailersApplication); @@ -3364,7 +3879,7 @@ public async Task CONTINUATION_Received_WithTrailers_Available(bool sendData) // 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 @@ await ExpectAsync(Http2FrameType.HEADERS, 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 @@ public async Task CONTINUATION_Received_StreamIdMismatch_ConnectionError() { 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 @@ await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, - expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock); + expectedErrorMessage: SR.net_http_hpack_incomplete_header_block); } [Theory] @@ -3457,7 +3973,7 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo { 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 @@ await WaitForStreamErrorAsync( 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 @@ public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudo 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 @@ public async Task CONTINUATION_Sent_WhenHeadersLargerThanFrameLength() 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 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYAndWaitsForStreams 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 @@ public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhe 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 @@ public async Task AcceptNewStreamsDuringClosingConnection() 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 @@ await ExpectAsync(Http2FrameType.DATA, 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 @@ public async Task AppDoesNotReadRequestBody_ResetsAndDrainsRequest(int intFinalF 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 @@ public async Task ResetStream_ResetsAndDrainsRequest(int intFinalFrameType) 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 4b1bced2303c..a59207ccda35 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.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.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 @@ public async Task HEADERS_Received_CustomMethod_Accepted() 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 @@ public async Task HEADERS_Received_CONNECTMethod_Accepted() 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 @@ public async Task HEADERS_Received_OPTIONSStar_LeftOutOfPath() 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 @@ public async Task HEADERS_Received_OPTIONSSlash_Accepted() 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 @@ await InitializeConnectionAsync(context => 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 @@ await InitializeConnectionAsync(context => 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 @@ public async Task HEADERS_Received_MissingAuthority_200Status() 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 @@ public async Task HEADERS_Received_EmptyAuthority_200Status() 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 @@ public async Task HEADERS_Received_MissingAuthorityFallsBackToHost_200Status() 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 @@ public async Task HEADERS_Received_EmptyAuthorityIgnoredOverHost_200Status() 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 @@ public async Task HEADERS_Received_AuthorityOverridesHost_200Status() 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 @@ public async Task HEADERS_Received_AuthorityOverridesInvalidHost_200Status() 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(context => 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 @@ await InitializeConnectionAsync(context => 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 @@ await InitializeConnectionAsync(context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await ExpectAsync(Http2FrameType.DATA, 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 @@ await InitializeConnectionAsync(context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await ExpectAsync(Http2FrameType.SETTINGS, 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 @@ await InitializeConnectionAsync(httpContext => 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 @@ await InitializeConnectionAsync(async httpContext => 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 @@ await InitializeConnectionAsync(async httpContext => 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 @@ await InitializeConnectionAsync(async httpContext => 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 @@ await InitializeConnectionAsync(async httpContext => 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 @@ void NonAsyncMethod() 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 @@ await InitializeConnectionAsync(async httpContext => 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 @@ await InitializeConnectionAsync(async httpContext => 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 @@ await InitializeConnectionAsync(async httpContext => 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 @@ await InitializeConnectionAsync(async httpContext => 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 @@ void NonAsyncMethod() 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 @@ await InitializeConnectionAsync(httpContext => 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 @@ await InitializeConnectionAsync(async httpContext => 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 @@ await InitializeConnectionAsync(async httpContext => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => // 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await WaitForStreamErrorAsync(1, Http2ErrorCode.NO_ERROR, expectedErrorMessage: 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 @@ await InitializeConnectionAsync(context => [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 d3d7e0e7ae45..f80e5ad386e7 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.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.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 @@ protected static IEnumerable> ReadRateRequestHeader 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 @@ public override void Dispose() 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 @@ protected Task StartStreamAsync(int streamId, IEnumerable(TaskCreationOptions.RunContinuationsAsynchronously); _runningStreams[streamId] = tcs; - var frame = new Http2Frame(); - frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); - - 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; - } + writableBuffer.WriteStartStream(streamId, GetHeadersEnumerator(headers), _headerEncodingBuffer, endStream); + return FlushAsync(writableBuffer); + } - 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 @@ protected Task SendHeadersWithPaddingAsync(int streamId, IEnumerable 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 @@ protected Task SendPushPromiseFrameAsync() 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 @@ internal async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags 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 @@ protected async Task SendIncompleteHeadersFrameAsync(int streamId) 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 @@ internal async Task SendContinuationAsync(int streamId, Http2ContinuationF 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 @@ internal async Task SendContinuationAsync(int streamId, Http2ContinuationF 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 @@ protected async Task SendIncompleteContinuationFrameAsync(int streamId) 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 @@ protected Task SendInvalidGoAwayFrameAsync() 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 @@ internal async Task ReceiveFrameAsync(uint maxFrameSize = 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 @@ internal async Task ReceiveFrameAsync(uint maxFrameSize = } 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 @@ private static long GetOutputResponseBufferSize(ServiceContext serviceContext) 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 23e8374e6fbe..02413c58053a 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 @@ public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_Cl _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 @@ await WaitForConnectionErrorAsyncDoNotCloseTransport( _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 @@ public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnMultipleStreams_Abo 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 @@ await ExpectAsync(Http2FrameType.DATA, 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 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP 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 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi 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 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter 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 @@ await ExpectAsync(Http2FrameType.DATA, 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 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon 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 @@ await ExpectAsync(Http2FrameType.DATA, 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 @@ await InitializeConnectionAsync(context => 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 @@ await InitializeConnectionAsync(async context => 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 @@ await ExpectAsync(Http2FrameType.DATA, 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 f52e83eb62d0..950a304ecb71 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 @@ public class TlsTests : LoggedTest 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 @@ private async Task ReceiveFrameAsync(PipeReader reader) 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 000000000000..a0445ab86ff9 --- /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 000000000000..f767fc08718c --- /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 45e21934be8f..43e557734e87 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs @@ -383,14 +383,13 @@ void ConfigureListenOptions(ListenOptions listenOptions) 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 @@ void ConfigureListenOptions(ListenOptions listenOptions) } [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 @@ public void ThrowsForCertificatesMissingServerEku(string testCertName) [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 90a8b1dbde54..163d6a632f10 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs @@ -143,6 +143,7 @@ public async Task EmptyRequestLoggedAsDebug() } [Fact] + [QuarantinedTest] public async Task ClientHandshakeFailureLoggedAsDebug() { var loggerProvider = new HandshakeErrorLoggerProvider(); @@ -218,6 +219,7 @@ await sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null, [Fact] [Repeat(20)] + [QuarantinedTest] public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnectionIsAborted() { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -371,7 +373,7 @@ public async Task ClientAttemptingToUseUnsupportedProtocolIsLoggedAsDebug() 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 @@ public async Task DevCertWithInvalidPrivateKeyProducesCustomWarning() 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 bab724834131..cad32b08bd5f 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 753e05814aeb..31539e50b3c8 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 fef0608c1c9e..dafbd6eca7ed 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -790,7 +790,7 @@ await connection.ReceiveEnd( } [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 @@ await connection.Receive( } } + [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 935a722029bb..0dca63fe5ffe 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs @@ -2541,7 +2541,7 @@ await connection.Send( } [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 1c76309881a1..0c8e40921da6 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 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action() { _transportFactory }, context); }); }); diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/ChromeTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/ChromeTests.cs index de82af3a9837..80cb4e890e50 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/ChromeTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/ChromeTests.cs @@ -62,7 +62,7 @@ private void InitializeArgs() }; } - [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 @@ public async Task Http2(string requestSuffix, string expectedResponse) .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 2c5f39382d9f..eb7a95a2281d 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/H2SpecTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/H2SpecTests.cs @@ -21,7 +21,6 @@ public class H2SpecTests : LoggedTest { [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 @@ public static TheoryData H2SpecTestCases 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 234c127379f1..18faacc44d28 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 @@ public static IEnumerable SupportedSchemes 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 @@ public static IEnumerable SupportedSchemes } } - [ConditionalTheory] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task HelloWorld(string scheme) { @@ -72,7 +72,7 @@ public async Task HelloWorld(string scheme) await host.StopAsync().DefaultTimeout(); } - [ConditionalTheory] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task Echo(string scheme) { @@ -102,7 +102,7 @@ public async Task Echo(string scheme) } // Concurrency testing - [ConditionalTheory] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task MultiplexGet(string scheme) { @@ -150,7 +150,7 @@ async Task RunRequest(string url) } // Concurrency testing - [ConditionalTheory] + [Theory] [MemberData(nameof(SupportedSchemes))] public async Task MultiplexEcho(string scheme) { @@ -201,7 +201,7 @@ async Task RunRequest(string url) 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 @@ static BulkContent() 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 @@ public static async Task VerifyContent(Stream stream) 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 @@ public static async Task VerifyContent(Stream stream) 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 @@ public async Task BidirectionalStreaming(string scheme) 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 @@ public async Task BidirectionalStreamingMoreClientData(string scheme) 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 @@ public async Task BidirectionalStreamingMoreClientData(string scheme) 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 @@ public async Task ReverseEcho(string scheme) 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 @@ public async Task ReverseEcho(string scheme) 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 @@ public async Task ReverseEcho(string scheme) 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 @@ public StreamingContent() { } + public Task SendStarted => _sendStarted.Task; + public async Task SendAsync(string text) { await _sendStarted.Task; @@ -514,46 +506,1146 @@ protected override bool TryComputeLength(out long length) length = 0; return false; } + + internal void Abort() + { + if (_sendComplete == null) + { + throw new InvalidOperationException("Sending hasn't started yet."); + } + _sendComplete.TrySetException(new Exception("Abort")); + } } - private static HttpClient CreateClient() + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ResponseTrailersWithoutData(string scheme) { - var handler = new HttpClientHandler(); - handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; - var client = new HttpClient(handler); - client.DefaultRequestVersion = HttpVersion.Version20; - return client; + 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(); } - private static HttpRequestMessage CreateRequestMessage(HttpMethod method, string url, HttpContent content) + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ResponseTrailersWithData(string scheme) { - return new HttpRequestMessage(method, url) - { - Version = HttpVersion.Version20, - Content = content, - }; + 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(); } - private static void ConfigureKestrel(IWebHostBuilder webHostBuilder, string scheme) + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ServerReset_BeforeResponse_ClientThrows(string scheme) { - webHostBuilder.UseKestrel(options => - { - options.Listen(IPAddress.Loopback, 0, listenOptions => + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => { - listenOptions.Protocols = HttpProtocols.Http2; - if (scheme == "https") + ConfigureKestrel(webHostBuilder, scheme); + webHostBuilder.ConfigureServices(AddTestLogging) + .Configure(app => app.Run(context => { - listenOptions.UseHttps(TestResources.GetTestCertificate()); - } + 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(); } - private static async Task ReadStreamHelloWorld(Stream stream) + [Theory] + [MemberData(nameof(SupportedSchemes))] + public async Task ServerReset_AfterHeaders_ClientBodyThrows(string scheme) { - var responseBuffer = new byte["Hello World".Length]; - var read = await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length).DefaultTimeout(); - Assert.Equal(responseBuffer.Length, read); + 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() + { + var handler = new HttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + var client = new HttpClient(handler); + client.DefaultRequestVersion = HttpVersion.Version20; + return client; + } + + private static HttpRequestMessage CreateRequestMessage(HttpMethod method, string url, HttpContent content) + { + return new HttpRequestMessage(method, url) + { + Version = HttpVersion.Version20, + Content = content, + }; + } + + private static void ConfigureKestrel(IWebHostBuilder webHostBuilder, string scheme) + { + webHostBuilder.UseKestrel(options => + { + options.Listen(IPAddress.Loopback, 0, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + if (scheme == "https") + { + listenOptions.UseHttps(TestResources.GetTestCertificate()); + } + }); + }); + } + + private static async Task ReadStreamHelloWorld(Stream stream) + { + var responseBuffer = new byte["Hello World".Length]; + 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 3a1b3e76709a..9fff0431d95b 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 a8223118a020..db41d1e1b40f 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/Utilities.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/Utilities.cs @@ -14,7 +14,7 @@ internal static bool CurrentPlatformSupportsAlpn() // "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 22cabcb14ac4..39871045f630 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 000000000000..2ff92f497f29 --- /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/tools/CodeGenerator/CodeGenerator.csproj b/src/Servers/Kestrel/tools/CodeGenerator/CodeGenerator.csproj index d0431dbb29d6..b79bf13aa714 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 c9a0ea251d6e..0e91c137d4e7 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 @@ object IFeatureCollection.this[Type key] 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 @@ object IFeatureCollection.this[Type key] {{ _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 @@ private IEnumerable> FastEnumerable() {{{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 000000000000..6cfabb996213 --- /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 eb687fccac99..48b1fa605f0e 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/Program.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/Program.cs @@ -26,12 +26,22 @@ public static int Main(string[] args) 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 @@ public static void Run( 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)) - { - File.WriteAllText(knownHeadersPath, knownHeadersContent); - } - - var existingHttpProtocolFeatureCollection = File.Exists(httpProtocolFeatureCollectionPath) ? File.ReadAllText(httpProtocolFeatureCollectionPath) : ""; - if (!string.Equals(httpProtocolFeatureCollectionContent, existingHttpProtocolFeatureCollection)) - { - File.WriteAllText(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent); - } + UpdateFile(knownHeadersPath, knownHeadersContent); + UpdateFile(httpProtocolFeatureCollectionPath, httpProtocolFeatureCollectionContent); + UpdateFile(httpUtilitiesPath, httpUtilitiesContent); + UpdateFile(http2ConnectionPath, http2ConnectionContent); + UpdateFile(transportMultiplexedConnectionFeatureCollectionPath, transportMultiplexedConnectionFeatureCollectionContent); + UpdateFile(transportConnectionFeatureCollectionPath, transportConnectionFeatureCollectionContent); + } - var existingHttpUtilities = File.Exists(httpUtilitiesPath) ? File.ReadAllText(httpUtilitiesPath) : ""; - if (!string.Equals(httpUtilitiesContent, existingHttpUtilities)) + public static void UpdateFile(string path, string content) + { + var existingContent = File.Exists(path) ? File.ReadAllText(path) : ""; + if (!string.Equals(content, existingContent)) { - File.WriteAllText(httpUtilitiesPath, httpUtilitiesContent); + File.WriteAllText(path, content); } - var existingTransportConnectionFeatureCollection = File.Exists(transportConnectionFeatureCollectionPath) ? File.ReadAllText(transportConnectionFeatureCollectionPath) : ""; - if (!string.Equals(transportConnectionFeatureCollectionContent, existingTransportConnectionFeatureCollection)) + var existingHttp2Connection = File.Exists(path) ? File.ReadAllText(path) : ""; + if (!string.Equals(content, existingHttp2Connection)) { - 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 000000000000..1689292eb51b --- /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 000000000000..8c0ad9d3ff99 --- /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 e04728890842..cf20b9d61712 100644 --- a/src/Servers/test/FunctionalTests/HelloWorldTest.cs +++ b/src/Servers/test/FunctionalTests/HelloWorldTest.cs @@ -21,7 +21,7 @@ public HelloWorldTests(ITestOutputHelper output) : base(output) 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 330586eafa86..75ef46207a56 100644 --- a/src/Servers/test/FunctionalTests/NtlmAuthenticationTest.cs +++ b/src/Servers/test/FunctionalTests/NtlmAuthenticationTest.cs @@ -22,7 +22,7 @@ public NtlmAuthenticationTests(ITestOutputHelper output) : base(output) 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 4f4af0b3e004..e4413435215d 100644 --- a/src/Servers/test/FunctionalTests/ResponseCompressionTests.cs +++ b/src/Servers/test/FunctionalTests/ResponseCompressionTests.cs @@ -33,7 +33,7 @@ public ResponseCompressionTests(ITestOutputHelper output) : base(output) 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 @@ public Task ResponseCompression_NoCompression(TestVariant variant) public static TestMatrix HostCompressionTestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Nginx) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] @@ -57,7 +57,7 @@ public Task ResponseCompression_HostCompression(TestVariant variant) 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 @@ public Task ResponseCompression_AppCompression(TestVariant variant) 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 169132f30b5d..c3738f9d1601 100644 --- a/src/Servers/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/test/FunctionalTests/ResponseTests.cs @@ -25,8 +25,8 @@ public ResponseTests(ITestOutputHelper output) : base(output) } 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 @@ public Task ResponseFormats_ManuallyChunk(TestVariant variant) 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 4dd2e88fd4a8..b3b461eafe6c 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 c2adffb4db8a..9fc54386b1f6 100644 --- a/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs +++ b/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs @@ -16,7 +16,7 @@ public class StartupNtlmAuthentication 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 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) }); 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 000000000000..4d05ebf58967 --- /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 000000000000..67ffa13f6fc0 --- /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 000000000000..517247811ec7 --- /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 000000000000..d16493a738e6 --- /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 000000000000..e5b0c9b43b8d --- /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 000000000000..f51bed2db997 --- /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 000000000000..5c6ba7ac3b83 --- /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 000000000000..1b59cb89c54c --- /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 000000000000..3faf5ac1cb78 --- /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 000000000000..9e0f947dc75d --- /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 000000000000..2267de01a68d --- /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 000000000000..87a01cf6c229 --- /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 4cbea8a86698..fe3e2eb51d04 100644 --- a/src/Shared/Buffers.MemoryPool/DiagnosticMemoryPool.cs +++ b/src/Shared/Buffers.MemoryPool/DiagnosticMemoryPool.cs @@ -119,7 +119,7 @@ protected override void Dispose(bool disposing) MemoryPoolThrowHelper.ThrowInvalidOperationException_DisposingPoolWithActiveBlocks(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray()); } - if (_blockAccessExceptions.Any()) + if (_blockAccessExceptions.Count > 0) { throw CreateAccessExceptions(); } @@ -136,7 +136,7 @@ protected override void Dispose(bool disposing) 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 99dfe082e575..a9baa560f532 100644 --- a/src/Shared/Buffers.MemoryPool/MemoryPoolSlab.cs +++ b/src/Shared/Buffers.MemoryPool/MemoryPoolSlab.cs @@ -58,7 +58,7 @@ protected void Dispose(bool disposing) _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 ef2a45cdebca..01962e83d4bf 100644 --- a/src/Shared/CertificateGeneration/CertificateManager.cs +++ b/src/Shared/CertificateGeneration/CertificateManager.cs @@ -807,7 +807,7 @@ public DetailedEnsureCertificateResult EnsureValidCertificateExists( 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 4f1700bb77c9..098e3d669076 100644 --- a/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs +++ b/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs @@ -288,7 +288,7 @@ public void DeleteCookie(HttpContext context, string key, CookieOptions options) 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 @@ public void DeleteCookie(HttpContext context, string key, CookieOptions options) 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 000000000000..379235f27425 --- /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 000000000000..4eac95982c20 --- /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 000000000000..ce608f65bc76 --- /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 000000000000..4e663773cc38 --- /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 000000000000..76fdf38f5e29 --- /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 000000000000..2be62b87faad --- /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 000000000000..92543e7f237e --- /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 000000000000..52c98b5eb251 --- /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 64b40242e044..cb24122c0a5f 100644 --- a/src/Shared/Diagnostics/BaseView.cs +++ b/src/Shared/Diagnostics/BaseView.cs @@ -102,11 +102,11 @@ protected void WriteAttributeValue(string thingy, int startPostion, object value 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 780817631863..8b3239c5dfa8 100644 --- a/src/Shared/E2ETesting/BrowserFixture.cs +++ b/src/Shared/E2ETesting/BrowserFixture.cs @@ -118,7 +118,7 @@ public async Task DisposeAsync() // 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 e31cbd93ac22..0e6552e7ec87 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 500d910a300b..f3e73276b087 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 d62b3ffb0012..fd0878aec2ff 100644 --- a/src/Shared/E2ETesting/SeleniumStandaloneServer.cs +++ b/src/Shared/E2ETesting/SeleniumStandaloneServer.cs @@ -86,10 +86,20 @@ private static async Task InitializeInstance(ITestOutputHelper output) 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 @@ private static async Task InitializeInstance(ITestOutputHelper output) // 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 @@ void LogOutput(object sender, DataReceivedEventArgs e) 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 000000000000..95c078e2c712 --- /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 8bc0f2c07cd5..94e674616998 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 1f74a4e33ac2..cc0dbc377cd9 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 4d3287c12dd6..1b16b5792cd4 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 000000000000..4df8b46b058f --- /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 000000000000..cb9f81123795 --- /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 000000000000..27772caa7231 --- /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 000000000000..55e496fa10d9 --- /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 000000000000..9a37aafffa89 --- /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 000000000000..c4c2c5ca4ed4 --- /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 000000000000..81e2b082f219 --- /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 000000000000..1bd01c17ec98 --- /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 000000000000..13a6ba4fc5aa --- /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 000000000000..a293c1c66b98 --- /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 6f861c239f61..4d0576c47703 100644 --- a/src/Shared/HttpSys/Constants.cs +++ b/src/Shared/HttpSys/Constants.cs @@ -15,8 +15,8 @@ internal static class Constants 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 aa5d884061c9..2e69d7bc4002 100644 --- a/src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs +++ b/src/Shared/HttpSys/NativeInterop/HttpApiTypes.cs @@ -51,6 +51,16 @@ internal enum HTTP_RESPONSE_INFO_TYPE HttpResponseInfoTypeQosProperty, } + internal enum HTTP_REQUEST_PROPERTY + { + HttpRequestPropertyIsb, + HttpRequestPropertyTcpInfoV0, + HttpRequestPropertyQuicStats, + HttpRequestPropertyTcpInfoV1, + HttpRequestPropertySni, + HttpRequestPropertyStreamError, + } + internal enum HTTP_TIMEOUT_TYPE { EntityBody, @@ -61,6 +71,11 @@ internal enum HTTP_TIMEOUT_TYPE MinSendRate, } + internal struct HTTP_REQUEST_PROPERTY_STREAM_ERROR + { + internal uint ErrorCode; + } + internal const int MaxTimeout = 6; [StructLayout(LayoutKind.Sequential)] @@ -88,6 +103,9 @@ internal struct HTTP_DATA_CHUNK [FieldOffset(8)] internal FromFileHandle fromFile; + + [FieldOffset(8)] + internal Trailers trailers; } [StructLayout(LayoutKind.Sequential)] @@ -106,6 +124,13 @@ internal struct FromFileHandle 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 @@ internal enum HTTP_VERB : int 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 504c434667d9..d1ed182f91ad 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 @@ public StringValues this[string key] } set { + ValidateRestrictedTrailers(key); ThrowIfReadOnly(); if (StringValues.IsNullOrEmpty(value)) { @@ -58,6 +89,7 @@ StringValues IDictionary.this[string key] get { return Store[key]; } set { + ValidateRestrictedTrailers(key); ThrowIfReadOnly(); ValidateHeaderCharacters(key); ValidateHeaderCharacters(value); @@ -105,6 +137,7 @@ public long? ContentLength } set { + ValidateRestrictedTrailers(HeaderNames.ContentLength); ThrowIfReadOnly(); if (value.HasValue) @@ -128,6 +161,7 @@ public long? ContentLength public void Add(KeyValuePair item) { + ValidateRestrictedTrailers(item.Key); ThrowIfReadOnly(); ValidateHeaderCharacters(item.Key); ValidateHeaderCharacters(item.Value); @@ -136,6 +170,7 @@ public void Add(KeyValuePair item) public void Add(string key, StringValues value) { + ValidateRestrictedTrailers(key); ThrowIfReadOnly(); ValidateHeaderCharacters(key); ValidateHeaderCharacters(value); @@ -144,6 +179,7 @@ public void Add(string key, StringValues value) public void Append(string key, string value) { + ValidateRestrictedTrailers(key); ThrowIfReadOnly(); ValidateHeaderCharacters(key); ValidateHeaderCharacters(value); @@ -214,6 +250,11 @@ private void ThrowIfReadOnly() { 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 @@ public static void ValidateHeaderCharacters(string headerCharacters) } } } + + 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 991571904bbe..a5294c5eb783 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 @@ internal static class HeaderEncoding 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 4108d901e2b9..790b858b3b9c 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 @@ internal SslStatus SslStatus 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 @@ internal void ReleasePins() 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 @@ internal string GetKnownHeader(HttpSysRequestHeader header) } 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 @@ internal void GetUnknownHeaders(IDictionary unknownHeaders 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 @@ private SocketAddress GetEndPoint(bool localEndpoint) } 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 @@ internal uint GetChunks(ref int dataChunkIndex, ref uint dataChunkOffset, byte[] } 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 @@ internal IReadOnlyDictionary> GetRequestInfo() } 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 @@ private IReadOnlyDictionary> GetRequestInfo(IntPtr bas 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 @@ internal X509Certificate2 GetClientCertificate() } 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 000000000000..6f54b2db47c0 --- /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 000000000000..dc635bb78997 --- /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 517c23e7a885..c1743a2f0a96 100644 --- a/src/ProjectTemplates/test/Helpers/ProcessEx.cs +++ b/src/Shared/Process/ProcessEx.cs @@ -5,6 +5,7 @@ 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 Microsoft.Extensions.Internal; using Xunit.Abstractions; -namespace Templates.Test.Helpers +namespace Microsoft.AspNetCore.Internal { internal class ProcessEx : IDisposable { @@ -100,6 +101,11 @@ public static ProcessEx Run(ITestOutputHelper output, string workingDirectory, s 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 @@ internal string GetFormattedOutput() { 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 @@ public void WaitForExit(bool assertSuccess, TimeSpan? timeSpan = null) { 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 8f6f4c324585..97a089267f8c 100644 --- a/src/Shared/RazorViews/BaseView.cs +++ b/src/Shared/RazorViews/BaseView.cs @@ -64,7 +64,7 @@ internal abstract class BaseView /// 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 @@ protected void WriteAttributeValue(string thingy, int startPostion, object value 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 000000000000..34cbeb5357f0 --- /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 1c5896fc0d15..cee167b20b0c 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 b81a5e886973..7a2dfdf3627c 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs +++ b/src/Shared/ServerInfrastructure/DuplexPipeStream.cs @@ -152,78 +152,22 @@ private async ValueTask ReadAsyncInternal(Memory destination, Cancell 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 8437ad334a27..5762b4a67121 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.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 @@ internal static class Http2FrameReader 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 @@ public static bool ReadFrame(in ReadOnlySequence readableBuffer, Http2Fram 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 @@ private static int ReadExtendedFields(Http2Frame frame, in ReadOnlySequence 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 000000000000..97284208e635 --- /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 000000000000..a23cac13d71f --- /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 000000000000..e6fe3d4ebec6 --- /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 000000000000..d53ced00310e --- /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 268d77a0e452..5468ae3c9a33 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 @@ public static unsafe bool TryGetAsciiString(byte* input, char* output, int count 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]); + return false; + } - 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]; + var tmp0 = Avx2.UnpackLow(vector, avxZero); + var tmp1 = Avx2.UnpackHigh(vector, avxZero); - input += sizeof(long); - output += sizeof(long); - } - if (input <= end - sizeof(int)) - { - isValid &= CheckBytesInAsciiRange(((int*)input)[0]); + // Bring into the right order + var out0 = Avx2.Permute2x128(tmp0, tmp1, 0x20); + var out1 = Avx2.Permute2x128(tmp0, tmp1, 0x31); - output[0] = (char)input[0]; - output[1] = (char)input[1]; - output[2] = (char)input[2]; - output[3] = (char)input[3]; + Avx.Store((ushort*)output, out0.AsUInt16()); + Avx.Store((ushort*)output + Vector256.Count, out1.AsUInt16()); - input += sizeof(int); - output += sizeof(int); - } + input += Vector256.Count; + output += Vector256.Count; + } while (input <= end - Vector256.Count); + + if (input == end) + { + return true; } - else + } + + if (input <= end - Vector128.Count) + { + do { - // 32-bit: Loop ints by default - while (input <= end - sizeof(int)) + var vector = Sse2.LoadVector128(input).AsSByte(); + if (!CheckBytesInAsciiRange(vector, zero)) { - isValid &= CheckBytesInAsciiRange(((int*)input)[0]); + return false; + } - output[0] = (char)input[0]; - output[1] = (char)input[1]; - output[2] = (char)input[2]; - output[3] = (char)input[3]; + var c0 = Sse2.UnpackLow(vector, zero).AsUInt16(); + var c1 = Sse2.UnpackHigh(vector, zero).AsUInt16(); - input += sizeof(int); - output += sizeof(int); - } - } - if (input <= end - sizeof(short)) - { - isValid &= CheckBytesInAsciiRange(((short*)input)[0]); + Sse2.Store((ushort*)output, c0); + Sse2.Store((ushort*)output + Vector128.Count, c1); - output[0] = (char)input[0]; - output[1] = (char)input[1]; + input += Vector128.Count; + output += Vector128.Count; + } while (input <= end - Vector128.Count); - input += sizeof(short); - output += sizeof(short); - } - if (input < end) + if (input == end) { - isValid &= CheckBytesInAsciiRange(((sbyte*)input)[0]); - output[0] = (char)input[0]; + return true; } - - return isValid; } - - // do/while as entry condition already checked - do + } + 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 @@ out Unsafe.AsRef>(output), 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 @@ out Unsafe.AsRef>(output), } [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 @@ ref Unsafe.Add(ref str, offset), 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 @@ private static bool WidenFourAsciiBytesToUtf16AndCompareToChars(ref char charSta 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 @@ private static bool WidenTwoAsciiBytesToUtf16AndCompareToChars(ref char charStar 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 @@ private unsafe static bool IsValidHeaderString(string value) 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 @@ public static string ConcatAsHexSuffix(string str, char separator, uint number) 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 @@ private static bool CheckBytesInAsciiRange(Vector check) 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 @@ private static bool CheckBytesInAsciiRange(long check) 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 000000000000..b627de76e668 --- /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 2d1dd20710ba..340fd68743fe 100644 --- a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs +++ b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs @@ -7,17 +7,20 @@ 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 @@ public IEnumerable GetDetails(Exception exception) 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 000000000000..17ef3a944c6b --- /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 b1c0ccc18871..8079f96072ec 100644 --- a/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs +++ b/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs @@ -33,7 +33,7 @@ public override string ToString() 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 ff6a4947f80e..000000000000 --- 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 1074097aa9fe..48f72438eb2c 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) + { + error = default; + return frames; + } + + List exceptions = null; + + for (var i = 0; i < stackFrames.Length; i++) { - var needFileInfo = true; - var stackTrace = new System.Diagnostics.StackTrace(exception, needFileInfo); - var stackFrames = stackTrace.GetFrames(); + var frame = stackFrames[i]; + var method = frame.GetMethod(); - if (stackFrames == null) + // Always show last stackFrame + if (!ShowInStackTrace(method) && i < stackFrames.Length - 1) { - return frames; + continue; } - for (var i = 0; i < stackFrames.Length; i++) + var stackFrame = new StackFrameInfo { - 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()); - } + StackFrame = frame, + FilePath = frame.GetFileName(), + LineNumber = frame.GetFileLineNumber(), + MethodDisplayInfo = GetMethodDisplayString(frame.GetMethod()), + }; - frames.Add(stackFrame); - } + 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 000000000000..3a05eb6b428a --- /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 000000000000..d08b9b043946 --- /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 5666f5b34fd0..9e01c15d5ec7 100644 --- a/src/Shared/UrlDecoder/UrlDecoder.cs +++ b/src/Shared/UrlDecoder/UrlDecoder.cs @@ -20,7 +20,7 @@ public static int DecodeRequestLine(ReadOnlySpan source, Span destin 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)); } diff --git a/src/Shared/ValueStopwatch/ValueStopwatch.cs b/src/Shared/ValueStopwatch/ValueStopwatch.cs new file mode 100644 index 000000000000..f99a084aebe0 --- /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 000000000000..65a341b3cc2f --- /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 100644 index 000000000000..28d1cb9083e6 --- /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 000000000000..f1cb32df8501 --- /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 100644 index 000000000000..d0f44f411e6a --- /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 71835890213d..5a8fdf170f0d 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 @@ public HeaderField this[int index] 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 @@ public void Resize(int maxSize) { 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 @@ private void EnsureAvailable(int available) { 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 000000000000..7f3b77558248 --- /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 000000000000..3fe3c86243d3 --- /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 000000000000..b30eba72b976 --- /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 000000000000..d09f1841349d --- /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 6911754476b7..792c871837a5 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 000000000000..2384c71983b6 --- /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 fed5481d305c..c534e0c17386 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 @@ public static (uint encoded, int bitLength) Encode(int data) /// 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 @@ public static int Decode(ReadOnlySpan src, Span dst) // 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 @@ internal static int DecodeValue(uint data, int validBits, out int decodedBits) // 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 @@ internal static int DecodeValue(uint data, int validBits, out int decodedBits) 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 000000000000..2442f02da0d5 --- /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 000000000000..0841d69bf28b --- /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 000000000000..227fbf0a447f --- /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 000000000000..b701fa79f41a --- /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 000000000000..785eab75bd82 --- /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 000000000000..f7ed1b96a5fd --- /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 000000000000..252d6b76b64b --- /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 000000000000..010d2372faee --- /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 000000000000..760446fa5cc8 --- /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 000000000000..c3402f255053 --- /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 000000000000..13fc509cc636 --- /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 000000000000..12594381bd18 --- /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 000000000000..e3b85872ed75 --- /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 000000000000..bbdba5d773ab --- /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 000000000000..c95609591062 --- /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 000000000000..9c5907a3a99a --- /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 000000000000..11a7e3473e23 --- /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 000000000000..46cd2ee685c8 --- /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 000000000000..ae4040621033 --- /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 000000000000..56a5a1167963 --- /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 000000000000..88297bdfdd51 --- /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 000000000000..f367d981bd10 --- /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 000000000000..2ecf0eb21065 --- /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 000000000000..0b9893333d71 --- /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 000000000000..757bb0da0545 --- /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 000000000000..58fc811f7cf4 --- /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 000000000000..0f19506b81a2 --- /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 000000000000..bca44c017d70 --- /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 000000000000..4164663df942 --- /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 000000000000..b7bd07d901da --- /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 000000000000..55c5e524ec57 --- /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 000000000000..2418a71ed6bf --- /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 000000000000..6eed08f02de8 --- /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 000000000000..d77bf1df76fb --- /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 000000000000..906f4562667c --- /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 000000000000..f533a8bb3818 --- /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 000000000000..1e96e1597acf --- /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 000000000000..25a5e5775511 --- /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 000000000000..3d294bce9c80 --- /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 000000000000..31ab416fbd39 --- /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 000000000000..72c35687ebe5 --- /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 000000000000..f08eb861d2e6 --- /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 000000000000..921808829dcb --- /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 000000000000..3e7d10a199a5 --- /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 000000000000..c2bbceb43cdd --- /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 000000000000..41f4b329983e --- /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 000000000000..843c2f75924c --- /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 000000000000..66a7e0d6dfb7 --- /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 000000000000..8fb0c1e33756 --- /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 000000000000..934c7a8e244e --- /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 000000000000..25cd145ee65e --- /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 000000000000..4e1af6dff60b --- /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 000000000000..6e25335f9992 --- /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 000000000000..f24980b8a22e --- /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 000000000000..6a756f9ca6f9 --- /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 000000000000..7dd7cc5991ee --- /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 000000000000..c6c8b9bd5b25 --- /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 000000000000..7891e16f461b --- /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 000000000000..a7420c8493b0 --- /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 000000000000..a8034b05a8b9 --- /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 000000000000..ef31f31a98da --- /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 000000000000..a706b05bfcb9 --- /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 000000000000..0bdc4a8f1dfd --- /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 000000000000..8840d87bb84b --- /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 000000000000..fab3e309791e --- /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 000000000000..a26fb7b1332d --- /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 83fe4babb77d..bc6eb225c123 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 000000000000..ef21ce5f3b01 --- /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 a5cb1605b38c..8775d692caa2 100644 --- a/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs +++ b/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs @@ -153,7 +153,7 @@ public int this[int something] // Not activatable } [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 000000000000..77312e0a0544 --- /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 657a310b6e24..b6a2d0c2bf08 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 @@ public void StackTraceHelper_IncludesLineNumbersForFiles() } // Act - var stackFrames = StackTraceHelper.GetFrames(exception); + var stackFrames = StackTraceHelper.GetFrames(exception, out _); // Assert Assert.Collection(stackFrames, @@ -55,7 +55,7 @@ public void StackTraceHelper_PrettyPrintsStackTraceForGenericMethods() 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 @@ public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithOutParameters() 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 @@ public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericOutParam 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 @@ public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithRefParameters() 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 @@ public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericRefParam 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 @@ public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithNullableParamet 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 @@ public void StackTraceHelper_PrettyPrintsStackTraceForMethodsOnGenericTypes() 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 @@ public void StackTraceHelper_ProducesReadableOutput() } // 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 @@ public void StackTraceHelper_DoesNotIncludeInstanceMethodsOnTypesWithStackTraceH 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 @@ public void StackTraceHelper_DoesNotIncludeStaticMethodsOnTypesWithStackTraceHid 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 @@ public void StackTraceHelper_DoesNotIncludeMethodsWithStackTraceHiddenAttribute( 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 @@ public void GetFrames_DoesNotFailForDynamicallyGeneratedAssemblies() 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 000000000000..bd29f647d10b --- /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 000000000000..fffc2c6656ef --- /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 000000000000..4abe4c5e6801 --- /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 d9318e01e999..2afdb299017c 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 @@ public class HPackDecoderTests : IHttpHeadersHandler // 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 @@ public class HPackDecoderTests : IHttpHeadersHandler public HPackDecoderTests() { _dynamicTable = new DynamicTable(DynamicTableInitialMaxSize); - _decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxRequestHeaderFieldSize, _dynamicTable); + _decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxHeaderFieldSize, _dynamicTable); + } + + void IHttpHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) + { + string headerName = Encoding.ASCII.GetString(name); + string headerValue = Encoding.ASCII.GetString(value); + + _decodedHeaders[headerName] = headerValue; + } + + void IHttpHeadersHandler.OnStaticIndexedHeader(int index) + { + // Not yet implemented for HPACK. + throw new NotImplementedException(); } - void IHttpHeadersHandler.OnHeader(Span name, Span value) + void IHttpHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan value) { - _decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiStringNonNullCharacters(); + // Not yet implemented for HPACK. + throw new NotImplementedException(); } - void IHttpHeadersHandler.OnHeadersComplete() { } + 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 @@ public void DecodesIndexedHeaderField_DynamicTable() _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 @@ public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName() [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName_HuffmanEncodedName() { - var encoded = _literalHeaderFieldWithIndexingNewName + byte[] encoded = _literalHeaderFieldWithIndexingNewName .Concat(_headerNameHuffman) .Concat(_headerValue) .ToArray(); @@ -157,7 +172,7 @@ public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName_HuffmanEnco [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName_HuffmanEncodedValue() { - var encoded = _literalHeaderFieldWithIndexingNewName + byte[] encoded = _literalHeaderFieldWithIndexingNewName .Concat(_headerName) .Concat(_headerValueHuffman) .ToArray(); @@ -168,7 +183,7 @@ public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName_HuffmanEnco [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName_HuffmanEncodedNameAndValue() { - var encoded = _literalHeaderFieldWithIndexingNewName + byte[] encoded = _literalHeaderFieldWithIndexingNewName .Concat(_headerNameHuffman) .Concat(_headerValueHuffman) .ToArray(); @@ -179,7 +194,7 @@ public void DecodesLiteralHeaderFieldWithIncrementalIndexing_NewName_HuffmanEnco [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_IndexedName() { - var encoded = _literalHeaderFieldWithIndexingIndexedName + byte[] encoded = _literalHeaderFieldWithIndexingIndexedName .Concat(_headerValue) .ToArray(); @@ -189,7 +204,7 @@ public void DecodesLiteralHeaderFieldWithIncrementalIndexing_IndexedName() [Fact] public void DecodesLiteralHeaderFieldWithIncrementalIndexing_IndexedName_HuffmanEncodedValue() { - var encoded = _literalHeaderFieldWithIndexingIndexedName + byte[] encoded = _literalHeaderFieldWithIndexingIndexedName .Concat(_headerValueHuffman) .ToArray(); @@ -203,15 +218,15 @@ public void DecodesLiteralHeaderFieldWithIncrementalIndexing_IndexedName_OutOfRa // 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 @@ public void DecodesLiteralHeaderFieldWithoutIndexing_NewName() [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedName() { - var encoded = _literalHeaderFieldWithoutIndexingNewName + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName .Concat(_headerNameHuffman) .Concat(_headerValue) .ToArray(); @@ -233,7 +248,7 @@ public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedName( [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedValue() { - var encoded = _literalHeaderFieldWithoutIndexingNewName + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName .Concat(_headerName) .Concat(_headerValueHuffman) .ToArray(); @@ -244,7 +259,7 @@ public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedValue [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedNameAndValue() { - var encoded = _literalHeaderFieldWithoutIndexingNewName + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName .Concat(_headerNameHuffman) .Concat(_headerValueHuffman) .ToArray(); @@ -255,7 +270,7 @@ public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedNameA [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_IndexedName() { - var encoded = _literalHeaderFieldWithoutIndexingIndexedName + byte[] encoded = _literalHeaderFieldWithoutIndexingIndexedName .Concat(_headerValue) .ToArray(); @@ -265,7 +280,7 @@ public void DecodesLiteralHeaderFieldWithoutIndexing_IndexedName() [Fact] public void DecodesLiteralHeaderFieldWithoutIndexing_IndexedName_HuffmanEncodedValue() { - var encoded = _literalHeaderFieldWithoutIndexingIndexedName + byte[] encoded = _literalHeaderFieldWithoutIndexingIndexedName .Concat(_headerValueHuffman) .ToArray(); @@ -279,15 +294,15 @@ public void DecodesLiteralHeaderFieldWithoutIndexing_IndexedName_OutOfRange_Erro // 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 @@ public void DecodesLiteralHeaderFieldNeverIndexed_NewName() [Fact] public void DecodesLiteralHeaderFieldNeverIndexed_NewName_HuffmanEncodedName() { - var encoded = _literalHeaderFieldNeverIndexedNewName + byte[] encoded = _literalHeaderFieldNeverIndexedNewName .Concat(_headerNameHuffman) .Concat(_headerValue) .ToArray(); @@ -309,7 +324,7 @@ public void DecodesLiteralHeaderFieldNeverIndexed_NewName_HuffmanEncodedName() [Fact] public void DecodesLiteralHeaderFieldNeverIndexed_NewName_HuffmanEncodedValue() { - var encoded = _literalHeaderFieldNeverIndexedNewName + byte[] encoded = _literalHeaderFieldNeverIndexedNewName .Concat(_headerName) .Concat(_headerValueHuffman) .ToArray(); @@ -320,7 +335,7 @@ public void DecodesLiteralHeaderFieldNeverIndexed_NewName_HuffmanEncodedValue() [Fact] public void DecodesLiteralHeaderFieldNeverIndexed_NewName_HuffmanEncodedNameAndValue() { - var encoded = _literalHeaderFieldNeverIndexedNewName + byte[] encoded = _literalHeaderFieldNeverIndexedNewName .Concat(_headerNameHuffman) .Concat(_headerValueHuffman) .ToArray(); @@ -334,7 +349,7 @@ public void DecodesLiteralHeaderFieldNeverIndexed_IndexedName() // 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 @@ public void DecodesLiteralHeaderFieldNeverIndexed_IndexedName_HuffmanEncodedValu // 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 @@ public void DecodesLiteralHeaderFieldNeverIndexed_IndexedName_OutOfRange_Error() // 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 @@ public void DecodesDynamicTableSizeUpdate() 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 @@ public void DecodesDynamicTableSizeUpdate_AfterIndexedHeaderStatic_Error() 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 @@ public void DecodesDynamicTableSizeUpdate_AfterIndexedHeaderStatic_SubsequentDec { 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 @@ public void DecodesDynamicTableSizeUpdate_AfterIndexedHeaderStatic_ResetAfterEnd { 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 @@ public void DecodesDynamicTableSizeUpdate_GreaterThanLimit_Error() 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 @@ public void DecodesStringLength_LimitConfigurable() [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 @@ public void DecodesIncompleteHeaderBlock_Error(byte[] encoded) [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 @@ private void TestDecode(byte[] encoded, string expectedHeaderName, string expect 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 000000000000..f7362aee4e3e --- /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 dfae6afe6e08..4383de415bf5 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 @@ public class HuffmanTests [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 @@ public void HuffmanDecodeArray(byte[] encoded, byte[] expected) [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 @@ public void ThrowsOnPaddingLongerThanSevenBits(byte[] encoded) [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 @@ public void ThrowsOnDestinationBufferTooSmall() [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 @@ public void HuffmanEncode(int code, uint expectedEncoded, int expectedBitLength) [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 @@ public void HuffmanDecode(int code, uint encoded, int bitLength) [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 @@ public static TheoryData HuffmanData { 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 000000000000..cd18bcb663f6 --- /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 000000000000..05ca293b6eae --- /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 000000000000..ba9e3dab6a9c --- /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 000000000000..05ca293b6eae --- /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 000000000000..b1d0655e4dea --- /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 000000000000..05ca293b6eae --- /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 000000000000..8451301a2007 --- /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 000000000000..05ca293b6eae --- /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 000000000000..70edf1609766 --- /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 000000000000..05ca293b6eae --- /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 000000000000..1533acbf5783 --- /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 000000000000..05ca293b6eae --- /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 000000000000..caab3cb22490 --- /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 000000000000..412ab63ef3eb --- /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 000000000000..eb62e9a4b131 --- /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 000000000000..27c6dbaf7153 --- /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 000000000000..2053b5210688 --- /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 000000000000..f93bba440c94 --- /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 000000000000..1159ae103ee6 --- /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 000000000000..57b6e1ae58fd --- /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 000000000000..7b550c9d32d3 --- /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 000000000000..77d3d58ca4bc --- /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 000000000000..216fb28d60e4 --- /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 084b5fbf71d1..ac3d241c045d 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 caf9a4b51471..8738545ffbde 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.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 @@ public async Task StopAsync(CancellationToken cancellationToken = default) ///

      /// 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 @@ private async Task StopAsyncCore(bool disposing) { 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 @@ -1207,11 +1219,13 @@ async Task StartProcessingInvocationMessages(ChannelReader in finally { invocationMessageChannel.Writer.TryComplete(); - await invocationMessageReceiveTask; timer.Stop(); await timerTask; uploadStreamSource.Cancel(); await HandleConnectionClose(connectionState); + + // await after the connection has been closed, otherwise could deadlock on a user's .On callback(s) + await invocationMessageReceiveTask; } } @@ -1626,6 +1640,7 @@ private class ConnectionState : IInvocationBinder { 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); @@ -1637,7 +1652,6 @@ private class ConnectionState : IInvocationBinder private long _nextActivationServerTimeout; private long _nextActivationSendPing; - private bool _hasInherentKeepAlive; public ConnectionContext Connection { get; } public Task ReceiveTask { get; set; } @@ -1766,7 +1780,10 @@ public async Task TimerLoop(TimerAwaitable timer) // 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(); @@ -1796,7 +1813,12 @@ public void ResetTimeout() // 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 df54c48fece9..c1aca1c27569 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 7bfb5b895c9a..56be28a14c7e 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 6dbd4e032e45..2220685e82ce 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.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 @@ private Func> GetHttpConnectionFactory(st 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 @@ bool ExpectedError(WriteContext writeContext) } 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 @@ bool ExpectedError(WriteContext writeContext) 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 @@ public async Task ClientCanConnectToServerWithLowerMinimumProtocol() 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 @@ public async Task CanSendAndReceiveMessage(string protocolName, HttpTransportTyp 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 @@ public async Task CanSendNull(string protocolName) 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 @@ public async Task CanStopAndStartConnection(string protocolName, HttpTransportTy 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 @@ public async Task CanAccessConnectionIdFromHubConnection(string protocolName, Ht 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 @@ public async Task CanStartConnectionFromClosedEvent(string protocolName, HttpTra 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 @@ public async Task MethodsAreCaseInsensitive(string protocolName, HttpTransportTy 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 @@ public async Task CanInvokeFromOnHandler(string protocolName, HttpTransportType 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 @@ public async Task StreamAsyncCoreTest(string protocolName, HttpTransportType tra 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 @@ async IAsyncEnumerable ClientStreamData(int value) 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 @@ public async Task StreamAsyncTest(string protocolName, HttpTransportType transpo 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 @@ public async Task StreamAsyncDoesNotStartIfTokenAlreadyCanceled(string protocolN 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 @@ bool ExpectedErrors(WriteContext writeContext) } 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 @@ bool ExpectedErrors(WriteContext writeContext) 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 @@ public async Task CanInvokeClientMethodFromServer(string protocolName, HttpTrans 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 @@ public async Task InvokeNonExistantClientMethodFromServer(string protocolName, H 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 @@ public async Task CanStreamClientMethodFromServer(string protocolName, HttpTrans 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 @@ public async Task CanStreamToAndFromClientInSameInvocation(string protocolName, 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 @@ async IAsyncEnumerable clientStreamData() 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 @@ async IAsyncEnumerable clientStreamData() 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 @@ await Assert.ThrowsAsync(async () => 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 @@ public async Task CanCloseStreamMethodEarly(string protocolName, HttpTransportTy 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 @@ bool ExpectedErrors(WriteContext writeContext) } 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 @@ bool ExpectedErrors(WriteContext writeContext) 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 @@ public async Task ServerThrowsHubExceptionIfHubMethodCannotBeResolved(string hub 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 @@ public async Task ServerThrowsHubExceptionIfHubMethodCannotBeResolvedAndArgument 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 @@ public async Task ServerThrowsHubExceptionOnHubMethodArgumentCountMismatch(strin 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 @@ public async Task ServerThrowsHubExceptionOnHubMethodArgumentTypeMismatch(string 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 @@ public async Task ServerThrowsHubExceptionIfStreamingHubMethodCannotBeResolved(s 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 @@ public async Task ServerThrowsHubExceptionOnStreamingHubMethodArgumentCountMisma 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 @@ public async Task ServerThrowsHubExceptionOnStreamingHubMethodArgumentTypeMismat 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 @@ public async Task ServerThrowsHubExceptionIfNonStreamMethodInvokedWithStreamAsyn 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 @@ public async Task ServerThrowsHubExceptionIfStreamMethodInvokedWithInvoke(string 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 @@ public async Task RandomGenericIsNotTreatedAsStream() 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 @@ public async Task RandomGenericIsNotTreatedAsStream() [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 @@ bool ExpectedErrors(WriteContext writeContext) 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 @@ bool ExpectedErrors(WriteContext writeContext) [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 @@ public async Task ClientCanUseJwtBearerTokenForAuthenticationWhenRedirected(Http [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 @@ public async Task ClientCanSendHeaders(HttpTransportType transportType) } } + [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 @@ public async Task WebSocketOptionsAreApplied() } } - [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 @@ public async Task CheckHttpConnectionFeatures() { 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 @@ public async Task CheckHttpConnectionFeatures() [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 @@ public async Task UserIdProviderCanAccessHttpContext() [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 @@ public async Task NegotiationSkipsServerSentEventsWhenUsingBinaryProtocol() [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 @@ bool ExpectedErrors(WriteContext writeContext) 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 @@ bool ExpectedErrors(WriteContext writeContext) 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 @@ bool ExpectedErrors(WriteContext writeContext) 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 c9e7b65e6632..b7f3206313be 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 @@ public class HubProtocolVersionTests : FunctionalTestBase [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 @@ public async Task ClientUsingOldCallWithOriginalProtocol(HttpTransportType trans [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 @@ public async Task ClientUsingOldCallWithNewProtocol(HttpTransportType transportT [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 @@ bool ExpectedErrors(WriteContext writeContext) 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 142e40546c50..a15f18faa1d1 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.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 @@ public async Task HttpConnectionSetsUserAgentOnAllRequests(HttpTransportType tra 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 @@ public async Task HttpConnectionSetsRequestedWithOnAllRequests(HttpTransportType 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 3c669ef94dd2..f1d191ee8ca8 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 @@ await AsyncUsing(CreateHubConnection(testConnection), async connection => }); } + [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 93cc3dd863a4..e254a79c5ce2 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 @@ public async Task ClientPingsMultipleTimes() 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 2c9df93cb8d4..e2b18c9857f4 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs @@ -345,7 +345,7 @@ public async Task UploadStreamCancelationSendsStreamComplete() var complete = await connection.ReadSentJsonAsync().OrTimeout(); Assert.Equal(HubProtocolConstants.CompletionMessageType, complete["type"]); Assert.EndsWith("canceled by client.", ((string)complete["error"])); - } + } } [Fact] @@ -414,6 +414,70 @@ bool ExpectedErrors(WriteContext writeContext) } } + [Fact] + public async Task CanAwaitInvokeFromOnHandlerWithServerClosingConnection() + { + using (StartVerifiableLog()) + { + var connection = new TestConnection(); + var hubConnection = CreateHubConnection(connection, loggerFactory: LoggerFactory); + await hubConnection.StartAsync().OrTimeout(); + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + hubConnection.On("Echo", async msg => + { + try + { + // This should be canceled when the connection is closed + await hubConnection.InvokeAsync("Echo", msg).OrTimeout(); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + + tcs.SetResult(null); + }); + + var closedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + hubConnection.Closed += _ => + { + closedTcs.SetResult(null); + + return Task.CompletedTask; + }; + + await connection.ReceiveJsonMessage(new { type = HubProtocolConstants.InvocationMessageType, target = "Echo", arguments = new object[] { "42" } }); + + // Read sent message first to make sure invoke has been processed and is waiting for a response + await connection.ReadSentJsonAsync().OrTimeout(); + await connection.ReceiveJsonMessage(new { type = HubProtocolConstants.CloseMessageType }); + + await closedTcs.Task.OrTimeout(); + + try + { + await tcs.Task.OrTimeout(); + Assert.True(false); + } + catch (TaskCanceledException) + { + } + } + } + + [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 fbc516e95cca..0da16cf1607f 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.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 @@ 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 @@ internal class TestConnection : ConnectionContext private readonly Func _onStart; private readonly Func _onDispose; + private readonly bool _hasInherentKeepAlive; public override string ConnectionId { get; set; } @@ -41,17 +43,22 @@ internal class TestConnection : ConnectionContext 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 @@ public async Task ReadSentTextMessageAsync(bool ignorePings = true) 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 4375d11285cd..acfdce101045 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 @@ private HttpClient CreateHttpClient() 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 22b41d56f396..6b6809b8377f 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 @@ static Constants() 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)) + { + return "Windows NT"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return "macOS"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return "Linux"; + } + else { - userAgent += "/" + assemblyVersion.InformationalVersion; + 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"; + } + + if (!string.IsNullOrEmpty(os)) + { + userAgent += $"; {os}"; + } + else + { + userAgent += "; Unknown OS"; + } + + userAgent += $"; {runtime}"; + + if (!string.IsNullOrEmpty(runtimeVersion)) + { + userAgent += $"; {runtimeVersion}"; + } + else + { + userAgent += "; Unknown Runtime Version"; + } + + userAgent += ")"; - UserAgentHeader = ProductInfoHeaderValue.Parse(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 6d4b5b8ffbf9..ed4caa274a9a 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 @@ internal class ServerSentEventsMessageParser 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 @@ public ParseResult ParseMessage(ReadOnlySequence buffer, out SequencePosit // 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 @@ public ParseResult ParseMessage(ReadOnlySequence buffer, out SequencePosit 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 @@ public void Reset() 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 @@ private void EnsureStartsWithDataPrefix(ReadOnlySpan line) 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 905a965841fe..e41dc35022d7 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 @@ public WebSocketsTransport(HttpConnectionOptions httpConnectionOptions, ILoggerF { _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 850f263a7d90..ee2e93c70739 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 eabba7738ec1..3e9534ce3967 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 61b170a40d57..b845d839497d 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 1948b9074f10..f3d88b1c2faf 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 838e6bc85a8e..407e71a6cff2 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 cccdd3d517fc..2fe81a7d95e4 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 f9553162f122..9618d8d9607c 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 44af7e998407..80beacb801d6 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 000000000000..6a1851569ef3 --- /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 78bf2cc36be6..000000000000 --- 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 36dca4f80886..5b04191f4c5e 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 Completable start() { 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 @@ private void stopConnection(String errorMessage) { 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 000000000000..977797af5fbf --- /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 000000000000..4c73a498f682 --- /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 3f1d7d5b3fda..52a6bf348708 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 @@ public void canSendNullArgInInvocation() { 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 @@ public void connectionSendsPingsRegularly() throws InterruptedException { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - TimeUnit.MILLISECONDS.sleep(100); + 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); + hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); + } - String[] sentMessages = mockTransport.getSentMessages(); - assertTrue(sentMessages.length > 1); - for (int i = 1; i < sentMessages.length; i++) { - assertEquals("{\"type\":6}" + RECORD_SEPARATOR, sentMessages[i]); - } + @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 02e2224565bc..6b65067c621b 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 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 @@ public Completable start(String url) { 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 @@ public String[] getSentMessages() { 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 000000000000..1a4256897c39 --- /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 646d328a5b6f..8dbdfd28f883 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 @@ public class EchoConnectionHandler : ConnectionHandler { 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 a762f99e554a..2f67e3d5cdec 100644 --- a/src/SignalR/clients/ts/FunctionalTests/Program.cs +++ b/src/SignalR/clients/ts/FunctionalTests/Program.cs @@ -30,9 +30,14 @@ public static void Main(string[] args) 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 f90738b44782..8f89229340d5 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 a7ad5a3886e5..8a3ccfa767f7 100644 --- a/src/SignalR/clients/ts/FunctionalTests/Startup.cs +++ b/src/SignalR/clients/ts/FunctionalTests/Startup.cs @@ -18,6 +18,7 @@ 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 @@ public void ConfigureServices(IServiceCollection services) }); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger) { if (env.IsDevelopment()) { @@ -120,6 +121,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 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 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) } } - 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 5bd4732c1a8b..04d88409a45b 100644 --- a/src/SignalR/clients/ts/FunctionalTests/TestHub.cs +++ b/src/SignalR/clients/ts/FunctionalTests/TestHub.cs @@ -10,6 +10,7 @@ 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 @@ public string GetContentTypeHeader() 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 85b58b4e287d..de0f56509710 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 8837d36e88fc..aff36430d900 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 5720a4e30ec5..ad6f65db08ac 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 2bb33c1c11bc..e7ed2cd66807 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 7760294123d0..14c1091b4957 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, - }); - - 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(); + 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!"; + 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, - }); + // 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(); + 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!"; + 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; + } + } - // 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, + // 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(); + }); }); - 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 3d5f434a1768..d58ef1f859da 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(); @@ -78,10 +79,11 @@ describe("hubConnection", () => { }); if (shouldRunHttpsTests) { - it("using https, can invoke server method and receive result", (done) => { + // xit will skip the test + xit("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 +110,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 +132,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 +156,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 +187,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 +221,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 +243,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 +265,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 +288,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 +315,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 +342,7 @@ describe("hubConnection", () => { }); it("can receive server calls", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -370,7 +372,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 +427,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 +448,7 @@ describe("hubConnection", () => { }); it("can handle different types", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -489,7 +491,7 @@ describe("hubConnection", () => { }); it("can receive different types", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -534,7 +536,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 +579,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 +596,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 +680,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 +687,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 +1099,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 7e82e91b9b93..a1f307e07240 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 8fb80c73b939..48220dea8b42 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= - 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" +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: + 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== - 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" +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 "~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: + 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: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" + 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= - dependencies: - kind-of "^3.0.2" - -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== +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: - is-plain-object "^2.0.4" + binary-extensions "^2.0.0" -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" - -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" +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== -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== - 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== +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: - 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= - 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== +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: - 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 e84037431991..d7687bcff64b 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 +yarn add @microsoft/signalr-protocol-msgpack ``` -or + +To try previews of the next version, use the `next` tag on NPM: + ```bash -yarn add @microsoft/signalr-protocol-msgpack +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 3d262cc056a5..58e4966e107a 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 72978faa2dec..61d5c7477c8f 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 66b0a893afee..edf8d8dc72e2 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 6d68509ff93b..5d4f4a5bc7f7 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" - -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= +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= -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== - 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= +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: - 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== - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= +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@^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== - 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== +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: - 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 ec8f34b2273b..921556949ec3 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 +yarn add @microsoft/signalr ``` -or + +To try previews of the next version, use the `next` tag on NPM: + ```bash -yarn add @microsoft/signalr +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 6b50f24d353b..75d0b4642c60 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 e6a6c1d99305..2aa54d01fe59 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 fece43020df9..7ed6033399e1 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 662bae5406eb..000000000000 --- 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 000000000000..2700660f6494 --- /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 9685feca5e9d..57614bb86b4e 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 075860006f17..e439ba159b9f 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 62190c166cd8..505631fa8d63 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 98a23a3b3d89..fa5d7432b938 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 a980370b643b..128872722bcf 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 7a250dbc41b5..9ed7338b6398 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 7ad1c7d1297e..f7a7da1784dd 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 c0435fd057ec..000000000000 --- 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 09463c5e70db..de4bf3e2b7a9 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 1233ce6c5190..3f7318cd6848 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 418c306055fc..4eb723f76e17 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 3b27bdded5de..41eba9c5648f 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 dc2086c66185..df4e0fba4a9b 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 d010fbdbc65d..67d889974e77 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 1cb682cabb59..797e0e1ea1a7 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 2e3dc670e49e..853b3527ec16 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 000000000000..1d06879acf7f --- /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 bad6a9fb3ac7..9fb038fa0f6f 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 0ad7d89a9548..0506d76ea925 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 d77f350616d1..49b63afe4056 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 1eaf631d5dab..ebdc6a07fb15 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 000000000000..f1e26f0d8895 --- /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 e26eaa86a3b3..cb4455a51770 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 75ce866f8985..5f3be06dda02 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 a1429d66f4dc..e971d5439fb6 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== - 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== +"@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== -ajv@^5.3.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= +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: - 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.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= +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== -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 57ac83c6a915..a93e6a9941eb 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 8ae1a927db91..83ec067e2074 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 f557b74f588a..d5d38749c772 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 @@ public static void WriteResponse(Microsoft.AspNetCore.Http.Connections.Negotiati 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 f557b74f588a..d5d38749c772 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 @@ public static void WriteResponse(Microsoft.AspNetCore.Http.Connections.Negotiati 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 dbf0e04aafe9..4277aaef64d0 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 a98e0ba94c64..ae69b56cddad 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 @@ private static AvailableTransport ParseAvailableTransport(ref Utf8JsonReader rea 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 e081a5c399a8..f0707f786afb 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 5ee369727c87..130b8a4a42d6 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 @@ public static partial class ConnectionEndpointRouteBuilderExtensions 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 @@ public partial class ConnectionOptionsSetup : Microsoft.Extensions.Options.IConf 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 @@ public static partial class HttpConnectionContextExtensions 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 @@ public NegotiateMetadata() { } 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 05227d6f3963..000000000000 --- 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 b76d5688f065..000000000000 --- 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 80f3d32800a5..5f5158a1fb3f 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 @@ public static void ReceivedDeleteRequestForUnsupportedTransport(ILogger logger, _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 c40a80b9ce97..969d4a593521 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 @@ private async Task ProcessDeleteAsync(HttpContext context) 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 @@ private async Task EnsureConnectionStateAsync(HttpConnectionContext connec 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 1e8738d9a960..dc545e48ba0f 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 33d72eddc2be..34626ca05bd6 100644 --- a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs +++ b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs @@ -1336,6 +1336,18 @@ public async Task RequestToActiveConnectionIdKillsPreviousConnectionLongPolling( 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 @@ public async Task TransferModeSet(HttpTransportType transportType, TransferForma [ConditionalFact] [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] - public async Task LongPollingKeepsWindowsIdentityBetweenRequests() + public async Task LongPollingKeepsWindowsPrincipalAndIdentityBetweenRequests() { using (StartVerifiableLog()) { @@ -1655,6 +1667,59 @@ public async Task LongPollingKeepsWindowsIdentityBetweenRequests() 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(); + + 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.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(); @@ -1668,6 +1733,8 @@ public async Task LongPollingKeepsWindowsIdentityBetweenRequests() // 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 68e086c560c2..d603c0d0830c 100644 --- a/src/SignalR/common/Http.Connections/test/MapConnectionHandlerTests.cs +++ b/src/SignalR/common/Http.Connections/test/MapConnectionHandlerTests.cs @@ -150,10 +150,10 @@ public void MapConnectionHandlerFindsAttributesFromEndPointAndOptions() 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 @@ public void MapConnectionHandlerEndPointRoutingFindsAttributesOnHub() 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 @@ void ConfigureRoutes(IEndpointRouteBuilder endpoints) .RequireAuthorization(new AuthorizeAttribute("Foo")); } - using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes)) + using (var host = BuildWebHost(ConfigureRoutes)) { host.Start(); @@ -253,7 +253,7 @@ void ConfigureRoutes(IEndpointRouteBuilder endpoints) endpoints.MapConnectionHandler("/path"); } - using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes)) + using (var host = BuildWebHost(ConfigureRoutes)) { host.Start(); @@ -281,7 +281,7 @@ void ConfigureRoutes(IEndpointRouteBuilder endpoints) endpoints.MapConnectionHandler("/path"); } - using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes)) + using (var host = BuildWebHost(ConfigureRoutes)) { host.Start(); @@ -308,7 +308,7 @@ public async Task MapConnectionHandlerWithWebSocketSubProtocolSetsProtocol() 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 @@ public override Task OnConnectedAsync(ConnectionContext connection) } } - private IWebHost BuildWebHostWithEndPointRouting(Action configure) + private IWebHost BuildWebHost(Action configure) { return new WebHostBuilder() .UseKestrel() @@ -405,12 +405,11 @@ private IWebHost BuildWebHost(string path, Action { -#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 704f0f4d270d..00d803ffdda5 100644 --- a/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs +++ b/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs @@ -18,6 +18,8 @@ public class NegotiateProtocolTests [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 19480b336fb9..e437919dfb21 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 28b4ec1d30f0..7cab28e40ba8 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 28b4ec1d30f0..7cab28e40ba8 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 1164497f9acd..46f5003a78f5 100644 --- a/src/SignalR/common/Protocols.Json/src/JsonHubProtocolOptions.cs +++ b/src/SignalR/common/Protocols.Json/src/JsonHubProtocolOptions.cs @@ -7,7 +7,7 @@ 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 dc3a19e0c5c5..98865bf6c9fc 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 a5696467bfd4..e8d5b514f0ec 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.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 @@ private void WriteCompletionMessage(CompletionMessage message, Utf8JsonWriter wr 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 @@ private void WriteStreamItemMessage(StreamItemMessage message, Utf8JsonWriter wr 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 @@ private void WriteArguments(object[] arguments, Utf8JsonWriter writer) 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 @@ private HubMessage ApplyHeaders(HubMessage message, Dictionary h 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 000000000000..6b409a9a5dc9 --- /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 b76c522f1ca9..43f4873c800b 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 78631d3c7f40..e1a762937bae 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.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 @@ public class MessagePackHubProtocol : IHubProtocol 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 @@ public MessagePackHubProtocol() 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 @@ public bool TryParseMessage(ref ReadOnlySequence input, IInvocationBinder 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) - { - 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; - } - - // Should be rare - return new ArraySegment(input.ToArray()); - } - - private static HubMessage ParseMessage(byte[] input, int startOffset, IInvocationBinder binder, IFormatterResolver resolver) + private static HubMessage ParseMessage(ref MessagePackReader reader, IInvocationBinder binder, MessagePackSerializerOptions msgPackSerializerOptions) { - var itemCount = MessagePackBinary.ReadArrayHeader(input, startOffset, out var readSize); - startOffset += readSize; + var itemCount = reader.ReadArrayHeader(); - 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 @@ private static HubMessage CreateInvocationMessage(byte[] input, ref int offset, 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 @@ private static HubMessage CreateInvocationMessage(byte[] input, ref int offset, // 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 @@ private static HubMessage CreateStreamInvocationMessage(byte[] input, ref int of // 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 @@ private static HubMessage CreateStreamItemMessage(byte[] input, ref int offset, 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 @@ private static CompletionMessage CreateCompletionMessage(byte[] input, ref int o 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 @@ private static CompletionMessage CreateCompletionMessage(byte[] input, ref int o 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 @@ private static CloseMessage CreateCloseMessage(byte[] input, ref int offset, int 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 @@ private static Dictionary ReadHeaders(byte[] input, ref int offs } } - 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 @@ private static string[] ReadStreamIds(byte[] input, ref int offset) 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 @@ private static object[] BindArguments(byte[] input, ref int offset, IReadOnlyLis 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 @@ private static T ApplyHeaders(IDictionary source, T destinati /// 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 { + var writer = new MessagePackWriter(memoryBufferWriter); + // Write message to a buffer so we can get its length - WriteMessageCore(message, writer); + WriteMessageCore(message, ref writer); - var dataLength = writer.Length; - var prefixLength = BinaryMessageFormatter.LengthPrefixLength(writer.Length); + 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; + return reader.ReadBoolean(); } - catch (Exception e) + catch (Exception ex) { - msgPackException = e; + throw new InvalidDataException($"Reading '{field}' as Boolean failed.", ex); } - - throw new InvalidDataException($"Reading '{field}' as Boolean failed.", msgPackException); } - private static int ReadInt32(byte[] input, ref int offset, string field) + private static int ReadInt32(ref MessagePackReader reader, string field) { - Exception msgPackException = null; try { - var readInt = MessagePackBinary.ReadInt32(input, offset, out var readSize); - offset += readSize; - return readInt; + return reader.ReadInt32(); } - catch (Exception e) + catch (Exception ex) { - msgPackException = e; + throw new InvalidDataException($"Reading '{field}' as Int32 failed.", ex); } - - throw new InvalidDataException($"Reading '{field}' as Int32 failed.", msgPackException); } - private static string ReadString(byte[] input, ref int offset, string field) + private static string ReadString(ref MessagePackReader reader, string field) { - Exception msgPackException = null; try { - var readString = MessagePackBinary.ReadString(input, offset, out var readSize); - offset += readSize; - return readString; + return reader.ReadString(); } - catch (Exception e) + catch (Exception ex) { - msgPackException = e; + throw new InvalidDataException($"Reading '{field}' as String failed.", ex); } - - throw new InvalidDataException($"Reading '{field}' as String failed.", msgPackException); } - private static long ReadMapLength(byte[] input, ref int offset, string field) + private static long ReadMapLength(ref MessagePackReader reader, string field) { - Exception msgPackException = null; try { - var readMap = MessagePackBinary.ReadMapHeader(input, offset, out var readSize); - offset += readSize; - return readMap; + return reader.ReadMapHeader(); } - catch (Exception e) + catch (Exception ex) { - msgPackException = e; + throw new InvalidDataException($"Reading map length for '{field}' failed.", ex); } - throw new InvalidDataException($"Reading map length for '{field}' failed.", msgPackException); } - private static long ReadArrayLength(byte[] input, ref int offset, string field) + private static long ReadArrayLength(ref MessagePackReader reader, string field) { - Exception msgPackException = null; try { - var readArray = MessagePackBinary.ReadArrayHeader(input, offset, out var readSize); - offset += readSize; - return readArray; + return reader.ReadArrayHeader(); } - catch (Exception e) + catch (Exception ex) { - msgPackException = e; + throw new InvalidDataException($"Reading array length for '{field}' failed.", ex); } - - 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) + private static object DeserializeObject(ref MessagePackReader reader, Type type, string field, MessagePackSerializerOptions msgPackSerializerOptions) { - 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 MessagePackSerializer.Deserialize(type, ref reader, msgPackSerializerOptions); } catch (Exception ex) { - msgPackException = ex; + throw new InvalidDataException($"Deserializing object of the `{type.Name}` type for '{field}' failed.", ex); } - - throw new InvalidDataException($"Deserializing object of the `{type.Name}` type for '{field}' failed.", msgPackException); } internal static List CreateDefaultFormatterResolvers() @@ -703,10 +666,10 @@ internal class SignalRResolver : IFormatterResolver { 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 @@ static Cache() } } } - - // 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 04f27fbe951f..73eff24cd107 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 1dc980d75047..c05c0397e660 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 @@ internal sealed class ReusableUtf8JsonWriter 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 ce6ebe8ce1be..b1ad909f3425 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 7b8c7751c818..7a0c3b361759 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 @@ public partial class CloseMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMes 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 @@ public static void WriteResponseMessage(Microsoft.AspNetCore.SignalR.Protocol.Ha 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 @@ public abstract partial class HubMethodInvocationMessage : Microsoft.AspNetCore. { 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 @@ public partial interface IHubProtocol 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 @@ internal PingMessage() { } 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 @@ public partial class StreamInvocationMessage : Microsoft.AspNetCore.SignalR.Prot 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 7b8c7751c818..7a0c3b361759 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 @@ public partial class CloseMessage : Microsoft.AspNetCore.SignalR.Protocol.HubMes 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 @@ public static void WriteResponseMessage(Microsoft.AspNetCore.SignalR.Protocol.Ha 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 @@ public abstract partial class HubMethodInvocationMessage : Microsoft.AspNetCore. { 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 @@ public partial interface IHubProtocol 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 @@ internal PingMessage() { } 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 @@ public partial class StreamInvocationMessage : Microsoft.AspNetCore.SignalR.Prot 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 8d374d387db7..17252e2b1f59 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 c2f02811e773..8cd0170c9f0b 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 @@ public void BinaryMessageParserThrowsForMessagesOver2GB(byte[] payload) { 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 27560c502a6b..9a88d32a5794 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 @@ public class HandshakeProtocolTests [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 a2f696ab17f2..6f9ba5cb60c9 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.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 @@ protected override IHubProtocol GetProtocolWithOptions(bool useCamelCase, bool i PayloadSerializerOptions = new JsonSerializerOptions() { IgnoreNullValues = ignoreNullValues, - PropertyNamingPolicy = useCamelCase ? JsonNamingPolicy.CamelCase : null + PropertyNamingPolicy = useCamelCase ? JsonNamingPolicy.CamelCase : null, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, } }; @@ -39,6 +41,7 @@ protected override IHubProtocol GetProtocolWithOptions(bool useCamelCase, bool i [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 @@ public void MagicCast() 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 6134d51f16ca..a38ad6e3e7b6 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 @@ public abstract class JsonHubProtocolTestsBase 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 @@ public abstract class JsonHubProtocolTestsBase 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 @@ public void ParseMessage(string protocolTestDataName) [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 @@ public void ReadToEndOfArgumentArrayOnError() 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 @@ public JsonProtocolTestData(string name, HubMessage message, bool useCamelCase, 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 34d97e10f3a8..0192641dadc0 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 @@ public void ParserDoesNotConsumePartialData(byte[] payload) 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 8134f8e9bf86..cf193576d77d 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 @@ public void SerializerCanSerializeTypesWithNoDefaultCtor() 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 @@ public void WriteAndParseDateTimeConvertsToUTC() // 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 255cdead83e6..de10b1dbf6e7 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 @@ protected override IHubProtocol GetProtocolWithOptions(bool useCamelCase, bool i [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 @@ public void CustomParseMessage(string protocolTestDataName) 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 b1fe8c76fb00..6b7a5563d37e 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 @@ public void GetAndReturnCachedBufferTextWriter() } [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 797c7a846af9..daa33a80210a 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 @@ private Func ResolveExpectedErrorsFilter(Func(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 b4d9a8ec8d54..a02e4bb1de92 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.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 @@ public class InProcessTestServer : InProcessTestServer 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 @@ internal override event Action ServerLogged 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; } - public InProcessTestServer(ILoggerFactory loggerFactory) + private InProcessTestServer() : this(loggerFactory: null, null) { + } + + private InProcessTestServer(ILoggerFactory loggerFactory, IDisposable disposable) + { + _extraDisposable = disposable; _logSinkProvider = new LogSinkProvider(); if (loggerFactory == null) @@ -71,11 +82,9 @@ public InProcessTestServer(ILoggerFactory loggerFactory) _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 @@ private void StartServer() .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 @@ private string RenderLogs(IList logs) 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 3271bd2ef557..9e69675720cb 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 ddb7dee2018a..fe7605fed7b8 100644 --- a/src/SignalR/common/testassets/Tests.Utils/TestClient.cs +++ b/src/SignalR/common/testassets/Tests.Utils/TestClient.cs @@ -120,7 +120,8 @@ public async Task> StreamAsync(string methodName, string[] str 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 @@ public async Task InvokeAsync(string methodName, params objec // 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 691338e3f948..4901e85969ad 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 f11b80bc74ad..a4c10f4eadfa 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 + +There are two other possible negotiation responses: -2. A redirect response which tells the client which URL and optionally access token to use as a result. +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 84f5308ae5b5..8bcc41b5725f 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 000000000000..8a386fd22845 --- /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 732ccb6af3e9..84027fff16dd 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 @@ internal static class Defaults 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 000000000000..738d3232fdba --- /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 1c1b18a058be..001d8138de94 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 a3443ecd946a..93f53ea3282b 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Program.cs +++ b/src/SignalR/perf/benchmarkapps/Crankier/Program.cs @@ -30,6 +30,7 @@ public static void Main(string[] args) 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 20f56cc72013..dc783bcc9e66 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 000000000000..1ab6a25abc18 --- /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 000000000000..44b8bb26f27a --- /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 000000000000..83f38aaf621d --- /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 000000000000..0b24b46e9b1c --- /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 000000000000..6b3154416c86 --- /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 fcb8c99caee3..8c7b2de4a4c8 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 654bf67bef79..e5f4fb420fa4 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 5309dae32c10..f23da84b5248 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 @@ protected HubCallerContext() { } } 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 @@ public static partial class HubClientsExtensions 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 @@ public static partial class HubClientsExtensions 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 @@ public static partial class HubClientsExtensions 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 @@ public static partial class HubClientsExtensions 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 @@ public static partial class HubClientsExtensions 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 @@ public virtual void Abort() { } 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 @@ public void Remove(Microsoft.AspNetCore.SignalR.HubConnectionContext connection) 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 @@ public void Reset() { } 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 @@ protected HubLifetimeManager() { } 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 @@ public partial class SerializedHubMessage { 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 02609bce1ecf..c00264ea2b06 100644 --- a/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs +++ b/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs @@ -82,10 +82,10 @@ public override Task RemoveFromGroupAsync(string connectionId, string groupName, /// 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 @@ private Task SendToAllConnections(string methodName, object[] args, Func 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 @@ public override Task SendGroupAsync(string groupName, string methodName, object[ // 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 @@ public override Task SendGroupsAsync(IReadOnlyList groupNames, string me 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 @@ public override Task SendGroupExceptAsync(string groupName, string methodName, o 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 @@ private HubMessage CreateInvocationMessage(string methodName, object[] args) /// 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 @@ public override Task OnDisconnectedAsync(HubConnectionContext connection) /// 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 8936bbc979a8..8f29cb89f3e0 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 @@ public static T AllExcept(this IHubClients hubClients, string excludedConn 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 @@ public static T Clients(this IHubClients hubClients, string connection1, s 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 @@ public static T Groups(this IHubClients hubClients, string group1, string 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 @@ public static T GroupExcept(this IHubClients hubClients, string groupName, 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 @@ public static T Users(this IHubClients hubClients, string user1, string us { 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 a62706d6a8d6..967db6ebb544 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 @@ public HubInvocationContext(HubCallerContext context, string hubMethodName, obje /// 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 1f5faf714d19..e625acfff8ae 100644 --- a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs +++ b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs @@ -489,12 +489,12 @@ private void InitializeHub(THub hub, HubConnectionContext connection) 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 @@ private void DiscoverHubMethods() 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 c6ad4fec1023..f868c2bcd703 100644 --- a/src/SignalR/server/Core/src/Internal/HubMethodDescriptor.cs +++ b/src/SignalR/server/Core/src/Internal/HubMethodDescriptor.cs @@ -132,13 +132,13 @@ private static Func> Compile 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 511280d636ef..b0343577cba5 100644 --- a/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs +++ b/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs @@ -20,6 +20,10 @@ internal static class TypedClientBuilder 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 @@ private static Func GenerateClientBuilder() 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 type = moduleBuilder.DefineType(name, TypeAttributes.Public, typeof(object), new[] { typeof(T) }); - var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private); + var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private | FieldAttributes.InitOnly); - BuildConstructor(type, proxyField); + 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 @@ private static IEnumerable GetAllInterfaceMethods(Type interfaceType } } - 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 = typeof(object).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, new Type[] { }, null); + var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, ParameterTypes); - 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 @@ private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo 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 @@ private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo 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 @@ private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo // 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 @@ private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo 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 @@ private static void VerifyInterface(Type interfaceType) foreach (var method in interfaceType.GetMethods()) { - VerifyMethod(interfaceType, method); + VerifyMethod(method); } foreach (var parent in interfaceType.GetInterfaces()) @@ -199,7 +230,7 @@ private static void VerifyInterface(Type interfaceType) } } - 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 0021c03cbd8c..de595a611b0d 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 @@ public static partial class HubEndpointRouteBuilderExtensions 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 @@ public static partial class GetHttpContextExtensions 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 cf9f694bb355..000000000000 --- 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 e01870193a52..000000000000 --- 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 afd93d2c42e2..c9f85fefbd11 100644 --- a/src/SignalR/server/SignalR/test/EndToEndTests.cs +++ b/src/SignalR/server/SignalR/test/EndToEndTests.cs @@ -36,7 +36,7 @@ public class EndToEndTests : FunctionalTestBase [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 @@ bool ExpectedErrors(WriteContext writeContext) 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 @@ bool ExpectedErrors(WriteContext writeContext) [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 @@ public async Task CanStartAndStopConnectionUsingGivenTransport(HttpTransportType [WebSocketsSupportedCondition] public async Task WebSocketsTest() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -125,7 +125,7 @@ public async Task WebSocketsTest() [WebSocketsSupportedCondition] public async Task WebSocketsReceivesAndSendsPartialFramesTest() { - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -164,7 +164,7 @@ public async Task WebSocketsReceivesAndSendsPartialFramesTest() [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 @@ public async Task HttpRequestsNotSentWhenWebSocketsTransportRequestedAndSkipNego [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 @@ public async Task HttpConnectionThrowsIfSkipNegotiationSetAndTransportIsNotWebSo [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 @@ public async Task ConnectionCanSendAndReceiveMessages(HttpTransportType transpor } [ConditionalTheory] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1383", FlakyOn.All)] [WebSocketsSupportedCondition] [InlineData(5 * 4096)] [InlineData(1000 * 4096 + 32)] @@ -316,7 +315,7 @@ public async Task ConnectionCanSendAndReceiveMessages(HttpTransportType transpor 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 @@ bool ExpectedErrors(WriteContext writeContext) writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -389,7 +388,7 @@ bool ExpectedErrors(WriteContext writeContext) writeContext.EventId.Name == "ErrorStartingTransport"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -419,7 +418,7 @@ bool ExpectedErrors(WriteContext writeContext) writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -455,7 +454,7 @@ bool ExpectedErrors(WriteContext writeContext) writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -525,7 +524,7 @@ public async Task ServerClosesConnectionWithErrorIfHubCannotBeCreated_LongPollin private async Task ServerClosesConnectionWithErrorIfHubCannotBeCreated(HttpTransportType transportType) { - using (StartServer(out var server)) + using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -580,45 +579,6 @@ private async Task ServerClosesConnectionWithErrorIfHubCannotBeCreated(HttpTrans } } - [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 @@ bool ExpectedErrors(WriteContext writeContext) writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (StartServer(out var server, ExpectedErrors)) + using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -658,53 +618,6 @@ bool ExpectedErrors(WriteContext writeContext) } } - [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 @@ bool ExpectedErrors(WriteContext writeContext) 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 3cae11661ad8..0de55bacef33 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs @@ -182,7 +182,7 @@ public Task SendToCaller(string message) 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 @@ public SelfRef() Self = this; } - public SelfRef Self; + public SelfRef Self { get; set; } } public async Task StreamingConcat(ChannelReader source) @@ -569,6 +569,13 @@ public void OverloadedMethod(string message) } } + public class GenericMethodHub : Hub + { + public void GenericMethod() + { + } + } + public class DisposeTrackingHub : TestHub { private readonly TrackDispose _trackDispose; @@ -660,7 +667,7 @@ public AsyncEnumerableImpl CounterAsyncEnumerableImpl(int count) 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 c3911252a5cc..aaf4cfa58257 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.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 @@ public async Task CannotCallDisposeMethodOnHub() } } + [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 @@ public static IEnumerable StreamingMethodAndHubProtocols nameof(StreamingHub.CounterAsyncEnumerable), nameof(StreamingHub.CounterAsyncEnumerableAsync), nameof(StreamingHub.CounterAsyncEnumerableImpl), - nameof(StreamingHub.AsyncEnumerableIsPreferedOverChannelReader), + nameof(StreamingHub.AsyncEnumerableIsPreferredOverChannelReader), }; foreach (var method in methods) @@ -2216,6 +2230,7 @@ public Task HandleAsync(AuthorizationHandlerContext context) { 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 @@ public IMessagePackFormatter GetFormatter() 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 @@ public async Task ConnectionTimesOutIfInitialPingAndThenNoMessages() } [Fact] + [QuarantinedTest] public async Task ReceivingMessagesPreventsConnectionTimeoutFromOccuring() { using (StartVerifiableLog()) @@ -3212,7 +3210,7 @@ public async Task UploadStringsToConcat() } } - [Fact(Skip = "Object not supported yet")] + [Fact] public async Task UploadStreamedObjects() { var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(); @@ -3276,7 +3274,7 @@ public async Task UploadManyStreams() } } - [Fact(Skip = "Cyclic parsing is not supported yet")] + [Fact] public async Task ConnectionAbortedIfSendFailsWithProtocolError() { using (StartVerifiableLog()) @@ -3292,8 +3290,6 @@ public async Task ConnectionAbortedIfSendFailsWithProtocolError() 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 @@ public async Task CanPassDerivedParameterToStreamHubMethod(string method) // 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 4f68f6fe7459..f88820cb78cf 100644 --- a/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs +++ b/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs @@ -75,6 +75,41 @@ public async Task SupportsSubInterfaces() 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 @@ public interface IInheritedClient : ITestClient 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 01bc4d2e3370..8f1a79dc4a22 100644 --- a/src/SignalR/server/SignalR/test/MapSignalRTests.cs +++ b/src/SignalR/server/SignalR/test/MapSignalRTests.cs @@ -31,41 +31,6 @@ public void NotAddingSignalRServiceThrows() 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 @@ public void MapHubFindsMultipleAuthAttributesOnDoubleAuthHub() 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 @@ public void MapHubEndPointRoutingFindsAttributesOnHubAndFromOptions() { 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 @@ void ConfigureRoutes(IEndpointRouteBuilder endpoints) .RequireAuthorization(new AuthorizeAttribute("Foo")); } - using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes)) + using (var host = BuildWebHost(ConfigureRoutes)) { host.Start(); @@ -295,7 +260,7 @@ void ConfigureRoutes(IEndpointRouteBuilder endpoints) endpoints.MapHub("/path"); } - using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes)) + using (var host = BuildWebHost(ConfigureRoutes)) { host.Start(); @@ -320,9 +285,7 @@ void ConfigureRoutes(IEndpointRouteBuilder endpoints) [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 @@ private class AuthHub : Hub { } - private IWebHost BuildWebHostWithEndPointRouting(Action configure) + private IWebHost BuildWebHost(Action configure) { return new WebHostBuilder() .UseKestrel() @@ -391,23 +354,5 @@ private IWebHost BuildWebHostWithEndPointRouting(Action c .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 25612f917f40..8ee6f7e53f82 100644 --- a/src/SignalR/server/SignalR/test/Startup.cs +++ b/src/SignalR/server/SignalR/test/Startup.cs @@ -71,18 +71,10 @@ public void Configure(IApplicationBuilder app) 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 000000000000..35675d450f72 --- /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 c40be2b32028..2c059c3edcfe 100644 --- a/src/SignalR/server/SignalR/test/WebSocketsTransportTests.cs +++ b/src/SignalR/server/SignalR/test/WebSocketsTransportTests.cs @@ -13,6 +13,7 @@ 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 @@ public void HttpOptionsSetOntoWebSocketOptions() [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 @@ await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/echo"), } } - [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 @@ await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/httpheader .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 @@ await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/httpheader [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 @@ await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/httpheader [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 @@ await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/echo"), [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 @@ public async Task WebSocketsTransportStopsWhenConnectionClosedByTheServer(Transf [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 1a6b2a74859d..7b431133c562 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 @@ 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 69a2c459c300..932181d40800 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 7780bca98850..000000000000 --- 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 76b5ed1b6eff..3126b52f5d49 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 @@ public RedisProtocol(DefaultHubMessageSerializer messageSerializer) // * 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 @@ public byte[] WriteInvocation(string methodName, object[] args, IReadOnlyList 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 @@ public byte[] WriteGroupCommand(RedisGroupCommand command) // * 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); - - return writer.ToArray(); + var writer = new MessagePackWriter(memoryBufferWriter); + + 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 @@ public byte[] WriteAck(int messageId) // * 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) { + var reader = new MessagePackReader(data); + // See WriteGroupCommand for format. - ValidateArraySize(ref data, 5, "GroupCommand"); + ValidateArraySize(ref reader, 5, "GroupCommand"); - 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); + 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 affff6ae4afe..731b94720d86 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 b34c7fb117b7..4497995673ad 100644 --- a/src/SignalR/server/StackExchangeRedis/src/RedisOptions.cs +++ b/src/SignalR/server/StackExchangeRedis/src/RedisOptions.cs @@ -30,7 +30,7 @@ public class RedisOptions 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 a498682567bd..ab063ee02a11 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 @@ public RedisServerFixture() _logger = _loggerFactory.CreateLogger>(); Docker.Default.Start(_logger); + } - FirstServer = StartServer(); - SecondServer = StartServer(); + public Task DisposeAsync() + { + return Task.CompletedTask; + } + + public async Task InitializeAsync() + { + FirstServer = await StartServer(); + SecondServer = await StartServer(); } - private InProcessTestServer 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 d25ea9035a29..221b37e42dce 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 18a0870512cf..5f51761a3134 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 1e9d62825872..6b7e0dc606cf 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 000000000000..b49dba01d926 --- /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 000000000000..f5a5314a8520 --- /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 000000000000..24567013d6dd --- /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 000000000000..51e35e83544a --- /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 000000000000..7a71ac34cb6d --- /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 000000000000..64ddc80ec683 --- /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 000000000000..244cad5a37db --- /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 000000000000..ef5e4d1f5a13 --- /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 000000000000..890ec2d160a7 --- /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 000000000000..d22779b6dd03 --- /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 000000000000..8a90acd5cd94 --- /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 000000000000..2b6a18dfc730 --- /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 000000000000..aa48eb8d51fd --- /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 000000000000..5ddad7b6454e --- /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 000000000000..7bf307373480 --- /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 000000000000..d76a0f177e2a --- /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 000000000000..9580bfd0da7e --- /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 000000000000..6a36575d70bf --- /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 000000000000..f99bf7361aca --- /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 000000000000..a702d71ecf16 --- /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 000000000000..fb79fd7bf79c --- /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 000000000000..52fa979133c0 --- /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 000000000000..b1895c1d922a --- /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 000000000000..6d4449ca92fa --- /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 000000000000..2c13e08eb344 --- /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 000000000000..d22a4996afbd --- /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 000000000000..b32fb36dad99 --- /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 000000000000..721966d6c5c0 --- /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 000000000000..239da937b8d5 --- /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 000000000000..0ed9e1a9a9b4 --- /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 000000000000..48fbbbfa3bed --- /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 000000000000..42773db21274 --- /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 000000000000..bbefa37427bf --- /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 000000000000..522cbd4624ca --- /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 000000000000..0a2dc1b21fa3 --- /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 000000000000..b34f0b715e40 --- /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 000000000000..a764db6622d0 --- /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 000000000000..e238d0769d81 --- /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 000000000000..2786a866b417 --- /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 000000000000..a09a17cf69f3 --- /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 000000000000..c3b9eba31d55 --- /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 000000000000..538a055792e9 --- /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 000000000000..e9a6b895ae89 --- /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 000000000000..2fbac5d90c81 --- /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 000000000000..e7f655a5bec0 --- /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 000000000000..7d809884d6ca --- /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 000000000000..0599e319011c --- /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 000000000000..b7719848a690 --- /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 caf322ee152f..ed06ed65055b 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 000000000000..34767b8574e1 --- /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 000000000000..19ee1098d2c2 --- /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 000000000000..4d016a07e784 --- /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 000000000000..50e3cae19217 --- /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 000000000000..2ddacacab98d --- /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 000000000000..350dcf1e1299 --- /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 000000000000..256e2b6cfccb --- /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 000000000000..3a69022b8857 --- /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 000000000000..1ee0b8cde8b6 --- /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 000000000000..85e82c11547f --- /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 000000000000..0fdf166f2b5a --- /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 000000000000..96dd93eb7cde --- /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 000000000000..a86f5645bf11 --- /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 000000000000..44448c74d191 --- /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 000000000000..24970cc281ef --- /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 000000000000..a5fa73019a08 --- /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 000000000000..3db8ffc0ceb2 --- /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 000000000000..8f131982f0f0 --- /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 000000000000..fefe6c5a42d2 --- /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 000000000000..e88a3334f29d --- /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 000000000000..12735057d3a7 --- /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 000000000000..cbc8e9adadc0 --- /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 000000000000..aa7354dca88d --- /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 000000000000..ede48243e5ab --- /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 000000000000..61d7802508a3 --- /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 000000000000..ca71d7063bd0 --- /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 000000000000..e18d828fbf4b --- /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 000000000000..a203e1289d04 --- /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 000000000000..a0a6e84d7df8 --- /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 000000000000..b218cd1ec5ef --- /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 000000000000..d4bc4f2b7418 --- /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 000000000000..6aeecaddccb6 --- /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 000000000000..417cd2d3fd03 --- /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 000000000000..5fc6c58041ad --- /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 000000000000..0d995fad5902 --- /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 000000000000..6b8df346c93e --- /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 000000000000..8df5e73c3027 --- /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 000000000000..f7ad603df551 --- /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 000000000000..44308160bd44 --- /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 000000000000..b9aed01e4138 --- /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 000000000000..944d706477d6 --- /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 000000000000..ff3c18e5525d --- /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 000000000000..b1c2fbf2f82d --- /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 000000000000..5a5f6aa85fb5 --- /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 c7d2d13e497f..347c0d82ee00 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.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 c50a0115fa5c..0c631a621177 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 e0d39cff01fc..40ddf13258e8 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 d3be27defb4e..5651ba46220d 100644 --- a/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs +++ b/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs @@ -29,7 +29,7 @@ public CertificateManagerTests(ITestOutputHelper output, CertFixture fixture) 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 @@ private void ListCertificates(ITestOutputHelper output) } [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 @@ public void EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAn } [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 @@ public void EnsureCreateHttpsCertificate_ReturnsExpiredCertificateIfVersionIsInc } [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 @@ public void EnsureCreateHttpsCertificate_ReturnsExpiredCertificateForEmptyVersio } [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 @@ public void EnsureCreateHttpsCertificate_ReturnsValidIfVersionIsZero() } [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 ebaffde463fd..7f61a987b348 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 9ad333bddcde..e5c004c2a556 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 8495b6de9d4d..7d998f77d733 100644 --- a/src/Tools/dotnet-user-secrets/src/CommandLineOptions.cs +++ b/src/Tools/dotnet-user-secrets/src/CommandLineOptions.cs @@ -19,7 +19,7 @@ public class CommandLineOptions 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 e54f04ff7c91..5b8b0385964b 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 @@ public void Execute(CommandContext context) 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 c2a81b5c2d72..1c00b4b7b09b 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.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 @@ public string Resolve(string project, string configuration) "/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 7fce951f0ca7..1ef774e1c5e7 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 d1558e881198..d299c208cab9 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 @@ public class InitCommandTests : IClassFixture 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 @@ public void AddsSpecificSecretIdToProject() } [Fact] + [QuarantinedTest] public void AddsEscapedSpecificSecretIdToProject() { const string SecretId = @"&"; @@ -88,6 +85,18 @@ public void DoesNotGenerateIdForProjectWithSecretId() 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 3a390d4439c7..48f6774b2bb0 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.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 @@ public class SecretManagerTests : IClassFixture { 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 @@ private Program CreateProgram() [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 @@ public void Error_InvalidProjectFormat() 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 @@ public void Error_Project_DoesNotExist() 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 @@ public void SupportsRelativePaths() 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 @@ public void SetSecrets(bool fromCurrentDirectory) 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 @@ public void SetSecrets(bool fromCurrentDirectory) { 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 @@ public void SetSecret_With_Verbose_Flag() 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 @@ public void List_Flattens_Nested_Objects() 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 @@ public void List_Json() 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 @@ public void Set_Flattens_Nested_Objects() 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 @@ public void Set_Flattens_Nested_Objects() } [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 @@ public void Clear_Secrets(bool fromCurrentDirectory) 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 @@ public void Clear_Secrets(bool fromCurrentDirectory) { 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 @@ public void Init_When_Project_Has_No_Secrets_Id() 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 adcbe32b1e5e..5a429036755b 100644 --- a/src/Tools/dotnet-user-secrets/test/UserSecretsTestFixture.cs +++ b/src/Tools/dotnet-user-secrets/test/UserSecretsTestFixture.cs @@ -35,7 +35,7 @@ public string GetTempSecretProject() 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 3c209122117c..ee7fc09d1509 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 9888258327c6..5954f540b59a 100644 --- a/src/Tools/dotnet-watch/test/AppWithDepsTests.cs +++ b/src/Tools/dotnet-watch/test/AppWithDepsTests.cs @@ -19,7 +19,8 @@ public AppWithDepsTests(ITestOutputHelper logger) _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 3e22d53245cb..553b7a4d11ea 100644 --- a/src/Tools/dotnet-watch/test/AwaitableProcess.cs +++ b/src/Tools/dotnet-watch/test/AwaitableProcess.cs @@ -21,6 +21,7 @@ public class AwaitableProcess : IDisposable private BufferBlock _source; private ITestOutputHelper _logger; private TaskCompletionSource _exited; + private bool _started; public AwaitableProcess(ProcessSpec spec, ITestOutputHelper logger) { @@ -71,10 +72,12 @@ public void Start() _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 @@ public void Dispose() 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 129d1219fad4..5d00c179ec83 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 @@ public void HelpArgs(string[] args) 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 @@ public void ParsesRemainingArgs(string[] args, string[] expected) 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 d6e57ee5cf34..d07f4e25a46d 100644 --- a/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs +++ b/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs @@ -22,8 +22,8 @@ public DotNetWatcherTests(ITestOutputHelper logger) _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 @@ public async Task RunsWithDotnetWatchEnvVariable() } [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 8667c06e7d06..9383b2d728aa 100644 --- a/src/Tools/dotnet-watch/test/GlobbingAppTests.cs +++ b/src/Tools/dotnet-watch/test/GlobbingAppTests.cs @@ -21,9 +21,10 @@ public GlobbingAppTests(ITestOutputHelper logger) _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 @@ public async Task ChangeCompiledFile(bool usePollingWatcher) 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 @@ public async Task DeleteCompiledFile() 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 @@ public async Task DeleteSourceFolder() 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 @@ public async Task RenameCompiledFile() 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 @@ public async Task ChangeExcludedFile() 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 4f2b42049275..b7171d5ad224 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 @@ public NoDepsAppTests(ITestOutputHelper logger) _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 @@ public async Task RestartProcessOnFileChange() 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 0e7dff9b823a..40c2af5214b9 100644 --- a/src/Tools/dotnet-watch/test/ProgramTests.cs +++ b/src/Tools/dotnet-watch/test/ProgramTests.cs @@ -28,13 +28,11 @@ public async Task ConsoleCancelKey() { _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 @@ public async Task ConsoleCancelKey() 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 44d49a203a27..6c40a01309f8 100644 --- a/src/Tools/dotnet-watch/test/Scenario/ProjectToolScenario.cs +++ b/src/Tools/dotnet-watch/test/Scenario/ProjectToolScenario.cs @@ -62,7 +62,7 @@ public void AddTestProjectFolder(string projectName) 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 @@ private void CreateTestDirectory() 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 6d89b4861aac..eeae109bf983 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.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 @@ public void Start(IEnumerable arguments, [CallerMemberName] string name }; 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 @@ public void Start(IEnumerable arguments, [CallerMemberName] string name 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 d0cde953c7ee..7399c1018d9b 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 9f015b1ee4fe..8f8043d0de9b 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 af6de1b33fd8..6de103d38298 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 95412443e657..110ff7686b5b 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 000000000000..81557e1bcaf6 --- /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 000000000000..4fbb9b15e3e8 --- /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 000000000000..ad8e11a40eae --- /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 000000000000..ad8e11a40eae --- /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 000000000000..72f5e369a1cc --- /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 000000000000..9b8efbd16f5e --- /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 000000000000..162ce4f6c1cb --- /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 000000000000..bef44616760c --- /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 000000000000..295bda63e8d5 --- /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 000000000000..2f5e770a0c37 --- /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 000000000000..0178bba2d5b9 --- /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 000000000000..baafedc4de96 --- /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 000000000000..3bf6fc1c9cd1 --- /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 8861abdde93a..a4a14ce447e4 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 0de7bb7d564c..c9613224fced 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%"