Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data
This commit is contained in:
15
.idea/.idea.RemoteControl/.idea/.gitignore
generated
vendored
Normal file
15
.idea/.idea.RemoteControl/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/modules.xml
|
||||
/contentModel.xml
|
||||
/projectSettingsUpdater.xml
|
||||
/.idea.RemoteControl.iml
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
4
.idea/.idea.RemoteControl/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.RemoteControl/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
8
.idea/.idea.RemoteControl/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.RemoteControl/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/.idea.RemoteControl/.idea/vcs.xml
generated
Normal file
7
.idea/.idea.RemoteControl/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
11
About/About.xml
Normal file
11
About/About.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0"?>
|
||||
<ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>RemoteControl</Name>
|
||||
<Author>TheQuux</Author>
|
||||
<Version>1.0</Version>
|
||||
<Description>Provides an API for controlling </Description>
|
||||
<WorkshopHandle>0</WorkshopHandle>
|
||||
<Tags>
|
||||
<Tag>LaunchPad</Tag>
|
||||
</Tags>
|
||||
</ModMetadata>
|
||||
BIN
About/Preview.png
Normal file
BIN
About/Preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
About/Thumb.png
Normal file
BIN
About/Thumb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 698 KiB |
36
BepInEx.cs
36
BepInEx.cs
@@ -1,36 +0,0 @@
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ExamplePatchMod
|
||||
{
|
||||
#region BepInEx
|
||||
[BepInEx.BepInPlugin(pluginGuid, pluginName, pluginVersion)]
|
||||
public class ExamplePatchMod : BepInEx.BaseUnityPlugin
|
||||
{
|
||||
public const string pluginGuid = "com.username.ExamplePatchMod";
|
||||
public const string pluginName = "ExamplePatchMod";
|
||||
public const string pluginVersion = "1.0";
|
||||
public static void Log(string line)
|
||||
{
|
||||
Debug.Log("[" + pluginName + "]: " + line);
|
||||
}
|
||||
void Awake()
|
||||
{
|
||||
try
|
||||
{
|
||||
var harmony = new Harmony(pluginGuid);
|
||||
harmony.PatchAll();
|
||||
Log("Patch succeeded");
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
Log("Patch Failed");
|
||||
Log(e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{81D8C460-4627-489B-8D5E-A0640866290F}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>ExamplePatchMod</RootNamespace>
|
||||
<AssemblyName>ExamplePatchMod</AssemblyName>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony">
|
||||
<HintPath>D:\SteamLibrary\steamapps\common\Stationeers\BepInEx\core\0Harmony.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp">
|
||||
<HintPath>D:\SteamLibrary\steamapps\common\Stationeers\rocketstation_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="BepInEx">
|
||||
<HintPath>D:\SteamLibrary\steamapps\common\Stationeers\BepInEx\core\BepInEx.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="UnityEngine">
|
||||
<HintPath>D:\SteamLibrary\steamapps\common\Stationeers\rocketstation_Data\Managed\UnityEngine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>D:\SteamLibrary\steamapps\common\Stationeers\rocketstation_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BepInEx.cs" />
|
||||
<Compile Include="Patches\ExamplePatchClass.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExamplePatchMod.Patches
|
||||
{
|
||||
internal class ExamplePatchClass
|
||||
{
|
||||
}
|
||||
}
|
||||
69
Patches/Tick.cs
Normal file
69
Patches/Tick.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Assets.Scripts;
|
||||
using Assets.Scripts.Objects.Electrical;
|
||||
using HarmonyLib;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace RemoteControl.Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(LogicStack), nameof(LogicStack.LogicStackTick))]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public class LogicStack_LogicStackTick
|
||||
{
|
||||
private struct State
|
||||
{
|
||||
internal object LockObj;
|
||||
internal bool Taken;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static void Prefix(out State __state)
|
||||
{
|
||||
__state = new State
|
||||
{
|
||||
LockObj = SubscriptionManager.Lock,
|
||||
Taken = false
|
||||
};
|
||||
if (!GameManager.RunSimulation) return;
|
||||
// System.Threading.Monitor.Enter(__state.LockObj, ref __state.Taken);
|
||||
try
|
||||
{
|
||||
RemoteControl.Log("logic stack tick: start prefix");
|
||||
SubscriptionManager.ApplyUpdates();
|
||||
RemoteControl.Log("logic stack tick: end prefix");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
RemoteControl.Log($"prefix: Exception {e}:\n {e.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static void Postfix(State __state)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!GameManager.RunSimulation) return;
|
||||
SubscriptionManager.RescanNetworks();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
RemoteControl.Log("logic stack tick: start postfix");
|
||||
RemoteControl.Log($"postfix: Exception {e}: \n{e.StackTrace}");
|
||||
RemoteControl.Log("logic stack tick: end postfix");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// if (__state.Taken)
|
||||
// {
|
||||
// System.Threading.Monitor.Exit(__state.LockObj);
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
14
Patches/WorldManagerStop.cs
Normal file
14
Patches/WorldManagerStop.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Assets.Scripts;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Remotecontrol.Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(WorldManager), nameof(WorldManager.StartWorld))]
|
||||
public class WorldManagerStartWorld
|
||||
{
|
||||
static void Prefix(WorldManager __instance)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ 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("ExamplePatchMod")]
|
||||
[assembly: AssemblyTitle("RemoteControl")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("ExamplePatchMod")]
|
||||
[assembly: AssemblyProduct("RemoteControl")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -15,9 +15,9 @@ Follow these steps to prepare your Stationeers mod project:
|
||||
4. **Update Project References**
|
||||
- In the **Solution Explorer**, press `CTRL + A` to select all files.
|
||||
- Press `CTRL + F` to open the **Find and Replace** window.
|
||||
- Find all instances of `ExamplePatchMod` and replace them with your mod name, e.g., `MyStationeersMod`.
|
||||
- Find all instances of `RemoteControl` and replace them with your mod name, e.g., `MyStationeersMod`.
|
||||
- Click **Replace All**.
|
||||
|
||||
5. **Update Project Properties**
|
||||
- On the top menu, go to **Project → [YourModName] Properties**.
|
||||
- Change both the **Assembly Name** and **Default Namespace** from `ExamplePatchMod` to your mod name.
|
||||
- Change both the **Assembly Name** and **Default Namespace** from `RemoteControl` to your mod name.
|
||||
|
||||
410
RemoteControl.csproj
Normal file
410
RemoteControl.csproj
Normal file
@@ -0,0 +1,410 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{81D8C460-4627-489B-8D5E-A0640866290F}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>RemoteControl</RootNamespace>
|
||||
<AssemblyName>RemoteControl</AssemblyName>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\Stationeers\BepInEx\core\0Harmony.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Assembly-CSharp">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\Stationeers\rocketstation_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="BepInEx">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\Stationeers\BepInEx\core\BepInEx.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System"/>
|
||||
<Reference Include="System.Core"/>
|
||||
<Reference Include="System.Xml.Linq"/>
|
||||
<Reference Include="System.Data.DataSetExtensions"/>
|
||||
<Reference Include="Microsoft.CSharp"/>
|
||||
<Reference Include="System.Data"/>
|
||||
<Reference Include="System.Net.Http"/>
|
||||
<Reference Include="System.Xml"/>
|
||||
<Reference Include="System.IO.Compression"/>
|
||||
<Reference Include="System.Text" />
|
||||
<Reference Include="System.Text.Json" />
|
||||
<Reference Include="UniTask">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\Stationeers\rocketstation_Data\Managed\UniTask.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\Stationeers\rocketstation_Data\Managed\UnityEngine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\Stationeers\rocketstation_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="netstandard" />
|
||||
|
||||
<Reference Include="System.IO.Compression.ZipFile" />
|
||||
<Reference Include="System.Web"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Patches\Tick.cs"/>
|
||||
<Compile Include="Patches\WorldManagerStop.cs"/>
|
||||
<Compile Include="Properties\AssemblyInfo.cs"/>
|
||||
<Compile Include="Scripts\Remotecontrol.cs"/>
|
||||
<Compile Include="Scripts\SubscriptionManager.cs"/>
|
||||
<Compile Include="Scripts\SubscriptionModule.cs"/>
|
||||
<Compile Include="Utils\Extensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Actions\ActionModule.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Actions\RedirectModule.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Authentication\Auth.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Authentication\BasicAuthenticationModule.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Authentication\BasicAuthenticationModuleBase.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Authentication\BasicAuthenticationModuleExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\CompressionMethod.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\CompressionMethodNames.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Cors\CorsModule.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\EmbedIOInternalErrorException.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\ExceptionHandler.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\ExceptionHandlerCallback.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\DirectoryLister.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\FileCache.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\FileCache.Section.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\FileModule.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\FileModuleExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\FileRequestHandler.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\FileRequestHandlerCallback.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\FileSystemProvider.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\IDirectoryLister.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\IFileProvider.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\Internal\Base64Utility.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\Internal\EntityTag.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\Internal\FileCacheItem.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\Internal\HtmlDirectoryLister.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\Internal\MappedResourceInfoExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\MappedResourceInfo.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\ResourceFileProvider.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Files\ZipFileProvider.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpContextExtensions-Items.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpContextExtensions-Redirect.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpContextExtensions-Requests.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpContextExtensions-RequestStream.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpContextExtensions-Responses.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpContextExtensions-ResponseStream.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpContextExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpException-Shortcuts.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpException.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpExceptionHandler.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpExceptionHandlerCallback.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpHeaderNames.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpListenerMode.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpNotAcceptableException.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpRangeNotSatisfiableException.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpRedirectException.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpRequestExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpResponseExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpStatusDescription.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\HttpVerbs.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\ICookieCollection.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IHttpContext.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IHttpContextHandler.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IHttpContextImpl.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IHttpException.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IHttpListener.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IHttpMessage.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IHttpRequest.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IHttpResponse.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IMimeTypeCustomizer.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IMimeTypeProvider.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Internal\BufferingResponseStream.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Internal\CompressionStream.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Internal\CompressionUtility.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Internal\DummyWebModuleContainer.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Internal\LockableNameValueCollection.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Internal\MimeTypeCustomizer.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Internal\RequestHandlerPassThroughException.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Internal\TimeKeeper.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Internal\UriUtility.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Internal\WebModuleCollection.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IWebModule.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IWebModuleContainer.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\IWebServer.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\MimeType.Associations.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\MimeType.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\MimeTypeCustomizerExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\ModuleGroup.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\CookieList.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\EndPointManager.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\HttpListener.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\EndPointListener.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\HeaderUtility.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\HttpConnection.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\HttpConnection.InputState.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\HttpConnection.LineState.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\HttpListenerContext.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\HttpListenerPrefixCollection.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\HttpListenerRequest.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\HttpListenerResponse.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\HttpListenerResponseHelper.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\ListenerPrefix.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\ListenerUri.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\NetExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\RequestStream.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\ResponseStream.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\StringExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\SystemCookieCollection.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\SystemHttpContext.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\SystemHttpListener.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\SystemHttpRequest.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\SystemHttpResponse.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Net\Internal\WebSocketHandshakeResponse.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\RequestDeserializer.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\RequestDeserializerCallback`1.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\RequestHandler.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\RequestHandlerCallback.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\ResponseSerializer.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\ResponseSerializerCallback.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\BaseRouteAttribute.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\Route.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RouteAttribute.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RouteHandlerCallback.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RouteMatch.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RouteMatcher.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RouteResolutionResult.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RouteResolverBase`1.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RouteResolverCollectionBase`2.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RouteVerbResolver.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RouteVerbResolverCollection.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RoutingModule.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RoutingModuleBase.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RoutingModuleExtensions-AddHandlerFromBaseOrTerminalRoute.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RoutingModuleExtensions-AddHandlerFromRouteMatcher.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RoutingModuleExtensions-AddHandlerFromTerminalRoute.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\RoutingModuleExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Routing\SyncRouteHandlerCallback.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Security\BanInfo.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Security\IIPBanningCriterion.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Security\Internal\IPBanningExecutor.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Security\IPBanningConfiguration.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Security\IPBanningModule.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Security\IPBanningModuleExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Security\IPBanningRegexCriterion.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Security\IPBanningRequestsCriterion.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Sessions\Internal\DummySessionProxy.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Sessions\ISession.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Sessions\ISessionManager.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Sessions\ISessionProxy.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Sessions\LocalSessionManager.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Sessions\LocalSessionManager.SessionImpl.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Sessions\Session.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Sessions\SessionExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Sessions\SessionProxy.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\ComponentCollectionExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\ComponentCollection`1.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\DisposableComponentCollection`1.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\HttpDate.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\IComponentCollection`1.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\IPParser.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\MimeTypeProviderStack.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\NameValueCollectionExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\QValueList.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\QValueListExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\StringExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\UniqueIdGenerator.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\UrlEncodedDataParser.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\UrlPath.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\Validate-MimeType.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\Validate-Paths.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\Validate-Rfc2616.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\Validate-Route.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\Utilities\Validate.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\FormDataAttribute.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\FormFieldAttribute.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\IRequestDataAttribute`1.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\IRequestDataAttribute`2.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\JsonDataAttribute.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\QueryDataAttribute.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\QueryFieldAttribute.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\WebApiController.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\WebApiModule.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\WebApiModuleBase.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebApi\WebApiModuleExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleBase.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleContainer.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleContainerExtensions-Actions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleContainerExtensions-Cors.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleContainerExtensions-Files.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleContainerExtensions-Routing.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleContainerExtensions-Security.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleContainerExtensions-WebApi.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleContainerExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleExtensions-ExceptionHandlers.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebModuleExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServer-Constants.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServer.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerBase`1.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerExtensions-ExceptionHandliers.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerExtensions-SessionManager.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerOptions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerOptionsBase.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerOptionsBaseExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerOptionsExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerState.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerStateChangedEventArgs.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebServerStateChangedEventHandler.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\CloseStatusCode.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\Fin.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\FragmentBuffer.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\Mask.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\MessageEventArgs.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\PayloadData.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\Rsv.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\StreamExtensions.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\SystemWebSocket.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\SystemWebSocketReceiveResult.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\WebSocket.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\WebSocketContext.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\WebSocketFrame.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\WebSocketFrameStream.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\WebSocketReceiveResult.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Internal\WebSocketStream.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\IWebSocket.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\IWebSocketContext.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\IWebSocketReceiveResult.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\Opcode.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\WebSocketException.cs"/>
|
||||
<Compile Include="Vendor\EmbedIO-3.5.2\WebSockets\WebSocketModule.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Collections\CollectionCacheRepository.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\CompositeHashCode.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Configuration\ConfiguredObject.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Configuration\SettingsProvider.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Cryptography\Hasher.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\DateTimeSpan.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Definitions.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Definitions.Types.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Diagnostics\Benchmark.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Diagnostics\BenchmarkUnit.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Diagnostics\HighResolutionTimer.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\EnumHelper.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Enums.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.ByteArrays.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.Dates.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.Dictionaries.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.Enumerable.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.Exceptions.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.Functional.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.IEnumerable.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.IPropertyProxy.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.Reflection.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.Strings.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.Tasks.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Extensions.ValueTypes.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Formatters\CsvReader.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Formatters\CsvWriter.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Formatters\HumanizeJson.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Formatters\Json.Converter.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Formatters\Json.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Formatters\Json.Deserializer.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Formatters\Json.Serializer.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Formatters\Json.SerializerOptions.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Formatters\JsonPropertyAttribute.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Formatters\JsonSerializerCase.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\FromString.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\InternalErrorException.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Logging\ConsoleLogger.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Logging\DebugLogger.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Logging\FileLogger.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Logging\ILogger.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Logging\Logger.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Logging\LogLevel.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Logging\LogMessageReceivedEventArgs.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Logging\TextLogger.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Mappers\CopyableAttribute.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Mappers\IObjectMap.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Mappers\ObjectMap.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Mappers\ObjectMapper.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Mappers\ObjectMapper.PropertyInfoComparer.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Net\Internal\IPAddressValue.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Net\IPAddressRange.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Net\IPAddressRangeExtensions.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Net\IPAddressUtility.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\ObjectComparer.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Paginator.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\ArgumentOptionAttribute.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\ArgumentParse.Validator.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\ArgumentParser.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\ArgumentParser.TypeResolver.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\ArgumentParserSettings.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\ExpressionParser.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\Operator.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\Token.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\Tokenizer.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\TokenType.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Parsers\VerbOptionAttribute.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Reflection\AttributeCache.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Reflection\ConstructorTypeCache.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Reflection\ExtendedTypeInfo.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Reflection\IPropertyProxy.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Reflection\MethodInfoCache.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Reflection\PropertyInfoProxy.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Reflection\PropertyTypeCache.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Reflection\TypeCache.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\SelfCheck.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\SingletonBase.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\StringConversionException.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\StructEndiannessAttribute.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\SwanRuntime.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Terminal.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Terminal.Graphics.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Terminal.Interaction.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Terminal.Output.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Terminal.Settings.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\TerminalWriters.Enums.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\AtomicBoolean.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\AtomicDateTime.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\AtomicDouble.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\AtomicEnum.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\AtomicInteger.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\AtomicLong.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\AtomicTimeSpan.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\AtomicTypeBase.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\CancellationTokenOwner.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\ExclusiveTimer.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\ISyncLocker.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\IWaitEvent.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\IWorker.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\IWorkerDelayProvider.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\PeriodicTask.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\RunnerBase.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\SyncLockerFactory.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\WaitEventFactory.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Threading\WorkerState.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Validators\IValidator.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Validators\ObjectValidationResult.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Validators\ObjectValidator.cs"/>
|
||||
<Compile Include="Vendor\Swan.Lite-3.1.0\Validators\Validators.cs"/>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36511.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExamplePatchMod", "ExamplePatchMod.csproj", "{81D8C460-4627-489B-8D5E-A0640866290F}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteControl", "RemoteControl.csproj", "{81D8C460-4627-489B-8D5E-A0640866290F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
246
Scripts/Remotecontrol.cs
Normal file
246
Scripts/Remotecontrol.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Assets.Scripts;
|
||||
using Assets.Scripts.Networks;
|
||||
using Assets.Scripts.Objects.Electrical;
|
||||
using Assets.Scripts.Objects.Motherboards;
|
||||
using BepInEx;
|
||||
using BepInEx.Configuration;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using HarmonyLib;
|
||||
using EmbedIO;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.WebApi;
|
||||
using RemoteControl.Message;
|
||||
using RemoteControl.Utils;
|
||||
using Swan;
|
||||
using GameDevice = Assets.Scripts.Objects.Pipes.Device;
|
||||
|
||||
|
||||
namespace RemoteControl
|
||||
{
|
||||
[BepInPlugin(pluginGuid, pluginName, pluginVersion)]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||
public class RemoteControl : BepInEx.BaseUnityPlugin
|
||||
{
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public static ConfigEntry<int> Port;
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public static ConfigEntry<bool> ListenOnAllInterfaces;
|
||||
|
||||
private readonly Harmony _harmony = new Harmony(pluginGuid);
|
||||
|
||||
public WebServer WebServer { get; private set; }
|
||||
|
||||
|
||||
private CancellationTokenSource _modLifecycle = new();
|
||||
|
||||
public const string pluginGuid = "com.thequux.stationeers.RemoteControl";
|
||||
public const string pluginName = "RemoteControl";
|
||||
public const string pluginVersion = "1.0";
|
||||
|
||||
public static void Log(string line)
|
||||
{
|
||||
Debug.Log("[" + pluginName + "]: " + line);
|
||||
}
|
||||
|
||||
private void StartRC()
|
||||
{
|
||||
if (!_modLifecycle.IsCancellationRequested)
|
||||
{
|
||||
_modLifecycle.Cancel();
|
||||
}
|
||||
|
||||
_modLifecycle = new CancellationTokenSource();
|
||||
|
||||
Port = Config.Bind(new ConfigDefinition("Server", "Port"), 39125,
|
||||
new ConfigDescription("The port to listen on"));
|
||||
ListenOnAllInterfaces = Config.Bind(
|
||||
new ConfigDefinition("Server", "ListenOnAllInterfaces"),
|
||||
false,
|
||||
new ConfigDescription("If set, listen on all interfaces. Otherwise, listen only on localhost"));
|
||||
|
||||
|
||||
var subscriptionModule = new SubscriptionModule("/subscribe");
|
||||
WebServer = new WebServer(o =>
|
||||
o.WithUrlPrefix($"http://{(ListenOnAllInterfaces.Value ? "0.0.0.0" : "localhost")}:{Port.Value}/")
|
||||
.WithEmbedIOHttpListener()
|
||||
)
|
||||
.WithWebApi("/api/v1", m => m.WithController<ApiController>())
|
||||
.WithModule(subscriptionModule);
|
||||
WebServer.Start(_modLifecycle.Token);
|
||||
|
||||
_harmony.PatchAll();
|
||||
foreach (var patchedMethod in _harmony.GetPatchedMethods())
|
||||
{
|
||||
Log($"Patched {patchedMethod.FullDescription()}");
|
||||
}
|
||||
Log($"Patched {_harmony.GetPatchedMethods().Count()} methods total");
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void OnLoaded(List<GameObject> prefabs)
|
||||
{
|
||||
// Start();
|
||||
#if DEVELOPMENT_BUILD
|
||||
Debug.Log($"Loaded {prefabs.Count} prefabs");
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnUnloaded(List<GameObject> prefabs)
|
||||
{
|
||||
// Stop();
|
||||
}
|
||||
|
||||
public void StopRC()
|
||||
{
|
||||
_modLifecycle.Cancel();
|
||||
_harmony.UnpatchSelf();
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
Log("Starting RemoteControl");
|
||||
StartRC();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Log("Tearing down RemoteControl");
|
||||
StopRC();
|
||||
}
|
||||
}
|
||||
|
||||
internal class ApiController : WebApiController
|
||||
{
|
||||
private static readonly Dictionary<string, LogicType> LtByName = EnumCollections.LogicTypes.AsLookupDict(true);
|
||||
private static readonly Dictionary<string, LogicSlotType> StByName = EnumCollections.LogicSlotTypes.AsLookupDict(true);
|
||||
|
||||
private static Device GetDeviceJson(GameDevice device) =>
|
||||
new()
|
||||
{
|
||||
ReferenceId = device.ReferenceId,
|
||||
Name = device.DisplayName,
|
||||
NameHash = device.GetNameHash(),
|
||||
PrefabHash = device.PrefabHash,
|
||||
PrefabName = device.PrefabName,
|
||||
LogicValues = EnumCollections.LogicTypes.Values.Where(ty => device.CanLogicRead(ty))
|
||||
.ToDictionary(ty => ty, ty => device.GetLogicValue(ty)),
|
||||
Slots = Enumerable.Range(0, device.TotalSlots)
|
||||
.Select(slot => EnumCollections.LogicSlotTypes.Values.Where(sty => device.CanLogicRead(sty, slot))
|
||||
.ToDictionary(ty => ty, sty=> device.GetLogicValue(sty, slot)))
|
||||
.ToList()
|
||||
};
|
||||
|
||||
private static Device? GetDeviceJson(long referenceId)
|
||||
{
|
||||
var dev = Referencable.Find<GameDevice>(referenceId);
|
||||
return dev == null ? null : GetDeviceJson(dev);
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Get, "/networks")]
|
||||
public Task<IList<string>> ListNetworks() => Task.FromResult(SubscriptionManager.GetProbes());
|
||||
|
||||
[Route(HttpVerbs.Get, "/networks/{networkId}")]
|
||||
public Task<IDictionary<long, Device>> ListDevices(string networkId)
|
||||
{
|
||||
// lock (SubscriptionManager.Lock)
|
||||
using (new LogTimer("ListDevices"))
|
||||
{
|
||||
var networks = new HashSet<CableNetwork>();
|
||||
GameDevice.AllDevices.ForEach(dev =>
|
||||
{
|
||||
if (dev is CableAnalyser analyser)
|
||||
{
|
||||
Debug.Log($"Found CA {analyser.DisplayName}: {analyser.ReferenceId}");
|
||||
networks.Add(analyser.CableNetwork);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
var devices = new Dictionary<long, Device>();
|
||||
foreach (var network in networks)
|
||||
{
|
||||
foreach (var device in network.DeviceList)
|
||||
{
|
||||
RemoteControl.Log($"Found {device.PrefabName}: {device.DisplayName}({device.ReferenceId})");
|
||||
if (devices.ContainsKey(device.ReferenceId)) continue;
|
||||
devices.Add(device.ReferenceId, GetDeviceJson(device));
|
||||
}
|
||||
}
|
||||
return Task.FromResult<IDictionary<long, Device>>(devices);
|
||||
}
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Post, "/networks/{networkId}/device/{deviceId}/logic/{varId}")]
|
||||
public async Task SetLogic(string networkId, long deviceId, string varId)
|
||||
{
|
||||
LogicType ltype;
|
||||
if (!LtByName.TryGetValue(varId.ToLowerInvariant(), out ltype))
|
||||
{
|
||||
throw HttpException.NotFound();
|
||||
}
|
||||
|
||||
var value = await HttpContext.GetRequestDataAsync<double>();
|
||||
|
||||
SubscriptionManager.SetLogic(networkId, deviceId, ltype, value);
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Post, "/networks/{networkId}/device/{deviceId}/slot/{slotId}/{varId}")]
|
||||
public async Task SetSlot(string networkId, long deviceId, int slotId, string varId)
|
||||
{
|
||||
LogicSlotType ltype;
|
||||
if (!StByName.TryGetValue(varId.ToLowerInvariant(), out ltype))
|
||||
{
|
||||
throw HttpException.NotFound();
|
||||
}
|
||||
|
||||
var value = await HttpContext.GetRequestDataAsync<double>();
|
||||
SubscriptionManager.SetSlot(networkId, deviceId, slotId, ltype, value);
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Patch, "/networks/{networkId}/device/{deviceId}")]
|
||||
public async Task PatchDevice(string networkId, long deviceId)
|
||||
{
|
||||
var values = await HttpContext.GetRequestDataAsync<Dictionary<string, double>>();
|
||||
if (values.Keys.Any(key => !LtByName.ContainsKey(key.ToLowerInvariant())))
|
||||
{
|
||||
throw HttpException.BadRequest();
|
||||
}
|
||||
|
||||
foreach (var keyValuePair in values)
|
||||
{
|
||||
SubscriptionManager.SetLogic(networkId, deviceId, LtByName[keyValuePair.Key.ToLowerInvariant()], keyValuePair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Get, "/networks/{networkId}/device/{deviceId}/code")]
|
||||
public async UniTask<string> GetCode(string networkId, long deviceId)
|
||||
{
|
||||
await UniTask.SwitchToMainThread(HttpContext.CancellationToken);
|
||||
|
||||
string source;
|
||||
// lock (SubscriptionManager.Lock)
|
||||
{
|
||||
var device = SubscriptionManager.GetDevice(networkId, deviceId) ?? throw HttpException.NotFound();
|
||||
if (device is not ICircuitHolder)
|
||||
{
|
||||
throw HttpException.NotFound();
|
||||
}
|
||||
|
||||
source = ((ICircuitHolder)device).GetSourceCode();
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
390
Scripts/SubscriptionManager.cs
Normal file
390
Scripts/SubscriptionManager.cs
Normal file
@@ -0,0 +1,390 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Assets.Scripts;
|
||||
using Assets.Scripts.Networks;
|
||||
using Assets.Scripts.Objects.Electrical;
|
||||
using Assets.Scripts.Objects.Motherboards;
|
||||
using Assets.Scripts.Util;
|
||||
using JetBrains.Annotations;
|
||||
using RemoteControl.Message;
|
||||
using Swan;
|
||||
using UnityEngine;
|
||||
using GameDevice = Assets.Scripts.Objects.Pipes.Device;
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
namespace RemoteControl
|
||||
{
|
||||
public class DataNetwork
|
||||
{
|
||||
public long ReferenceId { get; private set; }
|
||||
internal readonly List<CableAnalyser> Probes = new ();
|
||||
internal CableNetwork Network { get; private set; }
|
||||
|
||||
private readonly HashSet<long> _knownIds = new();
|
||||
|
||||
|
||||
internal readonly Dictionary<long, Device> DeviceCache = new();
|
||||
|
||||
public DataNetwork(CableNetwork network)
|
||||
{
|
||||
|
||||
ReferenceId = network.ReferenceId;
|
||||
Network = network;
|
||||
}
|
||||
|
||||
internal void RescanNetworkComposition()
|
||||
{
|
||||
_knownIds.Clear();
|
||||
_knownIds.UnionWith(Network.DataDeviceList.Select(device => device.ReferenceId));
|
||||
DeviceCache.Keys.Except(_knownIds).ToList().ForEach(device => DeviceCache.Remove(device));
|
||||
|
||||
_knownIds.Except(DeviceCache.Keys).ToList().ForEach(device =>
|
||||
{
|
||||
var newDev = new Device();
|
||||
var dev = Referencable.Find<GameDevice>(device);
|
||||
if (dev == null) return;
|
||||
newDev.ReferenceId = device;
|
||||
newDev.PrefabHash = dev.PrefabHash;
|
||||
newDev.PrefabName = dev.PrefabName;
|
||||
DeviceCache.Add(device, newDev);
|
||||
});
|
||||
UpdateAllDevices();
|
||||
}
|
||||
|
||||
private void UpdateAllDevices()
|
||||
{
|
||||
foreach (var kvp in DeviceCache)
|
||||
{
|
||||
var device = kvp.Value;
|
||||
var gameObj = Referencable.Find<GameDevice>(kvp.Key);
|
||||
if (gameObj == null) continue;
|
||||
device.LogicValues.Clear();
|
||||
foreach (var type in EnumCollections.LogicTypes.Values)
|
||||
{
|
||||
if (gameObj.CanLogicRead(type))
|
||||
{
|
||||
device.LogicValues[type] = gameObj.GetLogicValue(type);
|
||||
}
|
||||
}
|
||||
|
||||
var slotCount = gameObj.TotalSlots;
|
||||
while (device.Slots.Count > slotCount)
|
||||
{
|
||||
device.Slots.RemoveAt(device.Slots.Count - 1);
|
||||
}
|
||||
for (int slotIndex = 0; slotIndex < slotCount; slotIndex++)
|
||||
{
|
||||
if (device.Slots.Count <= slotIndex)
|
||||
{
|
||||
device.Slots.Add(new Dictionary<LogicSlotType, double>());
|
||||
}
|
||||
|
||||
foreach (var type in EnumCollections.LogicSlotTypes.Values)
|
||||
{
|
||||
if (gameObj.CanLogicRead(type, slotIndex))
|
||||
{
|
||||
device.Slots[slotIndex][type] = gameObj.GetLogicValue(type, slotIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public DataNetwork Reset(CableNetwork cnet)
|
||||
{
|
||||
ReferenceId = cnet.ReferenceId;
|
||||
Network = cnet;
|
||||
Probes.Clear();
|
||||
DeviceCache.Clear();
|
||||
_knownIds.Clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct LogicUpdate : IEquatable<LogicUpdate>
|
||||
{
|
||||
public readonly long TargetReferenceId;
|
||||
public readonly LogicType LogicType;
|
||||
|
||||
public LogicUpdate(long targetReferenceId, LogicType logicType)
|
||||
{
|
||||
TargetReferenceId = targetReferenceId;
|
||||
LogicType = logicType;
|
||||
}
|
||||
|
||||
public bool Equals(LogicUpdate other)
|
||||
{
|
||||
return TargetReferenceId == other.TargetReferenceId && LogicType == other.LogicType;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is LogicUpdate other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return CompositeHashCode.Using(TargetReferenceId, LogicType);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct SlotUpdate : IEquatable<SlotUpdate>
|
||||
{
|
||||
public readonly long ReferenceId;
|
||||
public readonly int Slot;
|
||||
public readonly LogicSlotType Type;
|
||||
|
||||
public SlotUpdate(long referenceID, int slot, LogicSlotType type)
|
||||
{
|
||||
ReferenceId = referenceID;
|
||||
Slot = slot;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public bool Equals(SlotUpdate other)
|
||||
{
|
||||
return ReferenceId == other.ReferenceId && Slot == other.Slot && Type == other.Type;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is SlotUpdate other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return CompositeHashCode.Using(ReferenceId, Slot, Type);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SubscriptionManager
|
||||
{
|
||||
public static readonly object Lock = new();
|
||||
|
||||
private static readonly Dictionary<long, DataNetwork> DataNetworks = new();
|
||||
// private Dictionary<string, List<RemoteControlLimpet>> _probesByName = new();
|
||||
// private Dictionary<long, RemoteControlLimpet> _probesById = new();
|
||||
private static readonly Dictionary<LogicUpdate, double> PendingUpdates = new();
|
||||
private static readonly Dictionary<SlotUpdate, double> PendingSlotUpdates = new();
|
||||
|
||||
[CanBeNull]
|
||||
public static Device FindCachedDevice(string probeName, long referenceID)
|
||||
{
|
||||
foreach (var analyzer in GetDataNetwork(probeName))
|
||||
{
|
||||
if (analyzer.DeviceCache.TryGetValue(referenceID, out var device))
|
||||
{
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Can be called from any thread
|
||||
public static bool SetLogic(string probeName, long referenceID, LogicType type, double value)
|
||||
{
|
||||
// lock (Lock)
|
||||
{
|
||||
var dev = FindCachedDevice(probeName, referenceID);
|
||||
if (dev == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
dev.LogicValues[type] = value;
|
||||
PendingUpdates[new LogicUpdate(referenceID, type)] = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Can be called from any thread
|
||||
public static bool SetSlot(string probeName, long referenceID, int slot, LogicSlotType type, double value)
|
||||
{
|
||||
// lock (Lock)
|
||||
{
|
||||
|
||||
var dev = FindCachedDevice(probeName, referenceID);
|
||||
if (dev != null && dev.Slots.Count > slot && dev.Slots[slot].ContainsKey(type))
|
||||
{
|
||||
dev.Slots[slot][type] = value;
|
||||
PendingSlotUpdates[new SlotUpdate(referenceID, slot, type)] = value;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static DataNetwork GetDataNetwork(CableNetwork network)
|
||||
{
|
||||
if (DataNetworks.TryGetValue(network.ReferenceId, out var dataNetwork))
|
||||
{
|
||||
return dataNetwork;
|
||||
}
|
||||
else
|
||||
{
|
||||
var ret = new DataNetwork(network);
|
||||
DataNetworks.Add(network.ReferenceId, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<DataNetwork> GetDataNetwork(string probeName)
|
||||
{
|
||||
// lock (Lock)
|
||||
{
|
||||
List<DataNetwork> networks = new();
|
||||
GameDevice.AllDevices.ForEach(dev =>
|
||||
{
|
||||
if (dev is CableAnalyser analyzer)
|
||||
{
|
||||
networks.Add(GetDataNetwork(analyzer.CableNetwork));
|
||||
}
|
||||
});
|
||||
return networks;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static GameDevice GetDevice(long referenceID)
|
||||
{
|
||||
var device = Referencable.Find<GameDevice>(referenceID);
|
||||
if (!DataNetworks.Values.Any((item) => item.Network.DataDeviceList.Contains(device)))
|
||||
return null;
|
||||
return device;
|
||||
|
||||
}
|
||||
|
||||
public static GameDevice GetDevice(string probeName, long referenceID)
|
||||
{
|
||||
var device = Referencable.Find<GameDevice>(referenceID);
|
||||
if (GetDataNetwork(probeName).Any(network => network.DeviceCache.ContainsKey(referenceID)))
|
||||
{
|
||||
return device;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called from Unity thread pool before the logic tick
|
||||
/// </summary>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public static void ApplyUpdates()
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
foreach (var update in PendingUpdates)
|
||||
{
|
||||
var device = GetDevice(update.Key.TargetReferenceId);
|
||||
|
||||
if (!device.CanLogicWrite(update.Key.LogicType))
|
||||
{
|
||||
device.SetLogicValue(update.Key.LogicType, update.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var update in PendingSlotUpdates)
|
||||
{
|
||||
var device = Referencable.Find<Assets.Scripts.Objects.Pipes.Device>(update.Key.ReferenceId);
|
||||
if (!DataNetworks.Values.Any((item) => item.Network.DataDeviceList.Contains(device)))
|
||||
continue;
|
||||
|
||||
if (!device.CanLogicWrite(update.Key.Type, update.Key.Slot))
|
||||
{
|
||||
device.SetLogicValue(update.Key.Type, update.Key.Slot, update.Value);
|
||||
}
|
||||
}
|
||||
|
||||
PendingUpdates.Clear();
|
||||
PendingSlotUpdates.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RescanNetworks()
|
||||
{
|
||||
using (new LogTimer("RescanNetworks"))
|
||||
{
|
||||
HashSet<CableAnalyser> scannedAnalyzers = new();
|
||||
HashSet<CableNetwork> scannedNetworks = new();
|
||||
|
||||
scannedNetworks.Clear();
|
||||
GameDevice.AllDevices.ForEach(dev =>
|
||||
{
|
||||
if (dev is CableAnalyser analyser)
|
||||
{
|
||||
scannedAnalyzers.Add(analyser);
|
||||
scannedNetworks.Add(analyser.CableNetwork);
|
||||
}
|
||||
});
|
||||
|
||||
var removed =
|
||||
new List<DataNetwork>(DataNetworks.Values.Where(dataNetwork =>
|
||||
!scannedNetworks.Contains(dataNetwork.Network)));
|
||||
foreach (var dataNet in removed)
|
||||
{
|
||||
DataNetworks.Remove(dataNet.ReferenceId);
|
||||
}
|
||||
|
||||
foreach (var dataNet in DataNetworks.Values)
|
||||
{
|
||||
dataNet.Probes.Clear();
|
||||
}
|
||||
|
||||
foreach (var analyzer in scannedAnalyzers)
|
||||
{
|
||||
if (DataNetworks.ContainsKey(analyzer.ReferenceId)) continue;
|
||||
|
||||
var dataNet = removed.Pop()?.Reset(analyzer.CableNetwork) ?? new DataNetwork(analyzer.CableNetwork);
|
||||
dataNet.Probes.Add(analyzer);
|
||||
}
|
||||
|
||||
|
||||
// TODO: when we have our own device for tagging a network, make this triggered by on the DataNetworkChange method
|
||||
foreach (var dataNet in DataNetworks.Values)
|
||||
{
|
||||
dataNet.RescanNetworkComposition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IList<string> GetProbes()
|
||||
{
|
||||
List<string> probes = new();
|
||||
GameDevice.AllDevices.ForEach(dev =>
|
||||
{
|
||||
if (dev is CableAnalyser)
|
||||
{
|
||||
probes.Add(dev.DisplayName);
|
||||
}
|
||||
});
|
||||
return probes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class LogTimer : IDisposable
|
||||
{
|
||||
private readonly DateTime _startTime = DateTime.Now;
|
||||
private string _action;
|
||||
|
||||
public LogTimer(string action)
|
||||
{
|
||||
RemoteControl.Log($"Beginning {action}");
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var endTime = DateTime.Now;
|
||||
var elapsed = endTime - _startTime;
|
||||
Debug.Log($"Time taken for {_action}: " + elapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Scripts/SubscriptionModule.cs
Normal file
74
Scripts/SubscriptionModule.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Assets.Scripts.Objects.Motherboards;
|
||||
using EmbedIO.WebSockets;
|
||||
using Swan;
|
||||
|
||||
namespace RemoteControl
|
||||
{
|
||||
internal class SubscriptionModule: WebSocketModule
|
||||
{
|
||||
private HashSet<IWebSocketContext> _jsonSubscribers = new();
|
||||
public void SendUpdate(Message.Update update)
|
||||
{
|
||||
if (_jsonSubscribers.Count > 0)
|
||||
{
|
||||
var encoded = System.Text.Encoding.UTF8.GetBytes(update.ToJson());
|
||||
foreach (var webSocketContext in _jsonSubscribers)
|
||||
{
|
||||
_ = SendAsync(webSocketContext, encoded);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SubscriptionModule(string urlPath, bool enableConnectionWatchdog = true) : base(urlPath, enableConnectionWatchdog)
|
||||
{
|
||||
AddProtocols("json");
|
||||
}
|
||||
|
||||
protected override Task OnMessageReceivedAsync(IWebSocketContext context, byte[] buffer, IWebSocketReceiveResult result)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override Task OnClientConnectedAsync(IWebSocketContext context)
|
||||
{
|
||||
_jsonSubscribers.Add(context);
|
||||
return base.OnClientConnectedAsync(context);
|
||||
}
|
||||
|
||||
protected override Task OnClientDisconnectedAsync(IWebSocketContext context)
|
||||
{
|
||||
_jsonSubscribers.Remove(context);
|
||||
return base.OnClientDisconnectedAsync(context);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Message
|
||||
{
|
||||
public class Update
|
||||
{
|
||||
public List<Network> Networks { get; set; } = new();
|
||||
}
|
||||
|
||||
public class Network
|
||||
{
|
||||
public long ReferenceId { get; set; }
|
||||
public List<string> Probes { get; set; } = new();
|
||||
|
||||
public List<Device> Devices { get; set; } = new();
|
||||
}
|
||||
|
||||
public class Device
|
||||
{
|
||||
public long ReferenceId { get; set; }
|
||||
public long NameHash{ get; set; }
|
||||
public string Name{ get; set; }
|
||||
public long PrefabHash{ get; set; }
|
||||
public string PrefabName{ get; set; }
|
||||
public Dictionary<LogicType, double> LogicValues { get; set; } = new();
|
||||
public List<Dictionary<LogicSlotType, double>> Slots { get; set; }= new();
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Utils/Extensions.cs
Normal file
29
Utils/Extensions.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Assets.Scripts;
|
||||
using Swan;
|
||||
|
||||
namespace RemoteControl.Utils
|
||||
{
|
||||
internal static class Extensions
|
||||
{
|
||||
internal static Dictionary<string, TEnum> AsLookupDict<TEnum, TValue>(this EnumCollection<TEnum, TValue> collection, bool lowercase)
|
||||
where TEnum : Enum, IConvertible, new()
|
||||
where TValue : IConvertible, IEquatable<TValue>
|
||||
{
|
||||
var result = new Dictionary<string, TEnum>();
|
||||
for (var i = 0; i < collection.Length; i++)
|
||||
{
|
||||
var name = collection.Names[i];
|
||||
if (lowercase)
|
||||
{
|
||||
name = name.ToLowerInvariant();
|
||||
}
|
||||
var value = collection.Values[i];
|
||||
result.Add(name, value);
|
||||
result.Add(collection.ValuesAsInts[i].ToStringInvariant(), value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Vendor/EmbedIO-3.5.2/Actions/ActionModule.cs
vendored
Normal file
54
Vendor/EmbedIO-3.5.2/Actions/ActionModule.cs
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// A module that passes requests to a callback.
|
||||
/// </summary>
|
||||
/// <seealso cref="WebModuleBase" />
|
||||
public class ActionModule : WebModuleBase
|
||||
{
|
||||
private readonly HttpVerbs _verb;
|
||||
|
||||
private readonly RequestHandlerCallback _handler;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ActionModule" /> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="verb">The HTTP verb that will be served by this module.</param>
|
||||
/// <param name="handler">The callback used to handle requests.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="handler"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="WebModuleBase(string)"/>
|
||||
public ActionModule(string baseRoute, HttpVerbs verb, RequestHandlerCallback handler)
|
||||
: base(baseRoute)
|
||||
{
|
||||
_verb = verb;
|
||||
_handler = Validate.NotNull(nameof(handler), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ActionModule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public ActionModule(RequestHandlerCallback handler)
|
||||
: this("/", HttpVerbs.Any, handler)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsFinalHandler => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
if (_verb != HttpVerbs.Any && context.Request.HttpVerb != _verb)
|
||||
return;
|
||||
|
||||
await _handler(context).ConfigureAwait(false);
|
||||
context.SetHandled();
|
||||
}
|
||||
}
|
||||
}
|
||||
99
Vendor/EmbedIO-3.5.2/Actions/RedirectModule.cs
vendored
Normal file
99
Vendor/EmbedIO-3.5.2/Actions/RedirectModule.cs
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// A module that redirects requests.
|
||||
/// </summary>
|
||||
/// <seealso cref="WebModuleBase" />
|
||||
public class RedirectModule : WebModuleBase
|
||||
{
|
||||
private readonly Func<IHttpContext, bool>? _shouldRedirect;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RedirectModule"/> class
|
||||
/// that will redirect all served requests.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="redirectUrl">The redirect URL.</param>
|
||||
/// <param name="statusCode">The response status code; default is <c>302 - Found</c>.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="redirectUrl"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="redirectUrl"/> is not a valid URL.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="statusCode"/> is not a redirection (3xx) status code.</para>
|
||||
/// </exception>
|
||||
/// <seealso cref="WebModuleBase(string)"/>
|
||||
public RedirectModule(string baseRoute, string redirectUrl, HttpStatusCode statusCode = HttpStatusCode.Found)
|
||||
: this(baseRoute, redirectUrl, null, statusCode, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RedirectModule"/> class
|
||||
/// that will redirect all requests for which the <paramref name="shouldRedirect"/> callback
|
||||
/// returns <see langword="true"/>.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="redirectUrl">The redirect URL.</param>
|
||||
/// <param name="shouldRedirect">A callback function that returns <see langword="true"/>
|
||||
/// if a request must be redirected.</param>
|
||||
/// <param name="statusCode">The response status code; default is <c>302 - Found</c>.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="redirectUrl"/> is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="shouldRedirect"/> is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="redirectUrl"/> is not a valid URL.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="statusCode"/> is not a redirection (3xx) status code.</para>
|
||||
/// </exception>
|
||||
/// <seealso cref="WebModuleBase(string)"/>
|
||||
public RedirectModule(string baseRoute, string redirectUrl, Func<IHttpContext, bool>? shouldRedirect, HttpStatusCode statusCode = HttpStatusCode.Found)
|
||||
: this(baseRoute, redirectUrl, shouldRedirect, statusCode, true)
|
||||
{
|
||||
}
|
||||
|
||||
private RedirectModule(string baseRoute, string redirectUrl, Func<IHttpContext, bool>? shouldRedirect, HttpStatusCode statusCode, bool useCallback)
|
||||
: base(baseRoute)
|
||||
{
|
||||
RedirectUrl = Validate.Url(nameof(redirectUrl), redirectUrl);
|
||||
|
||||
var status = (int)statusCode;
|
||||
if (status < 300 || status > 399)
|
||||
throw new ArgumentException("Status code does not imply a redirection.", nameof(statusCode));
|
||||
|
||||
StatusCode = statusCode;
|
||||
_shouldRedirect = useCallback ? Validate.NotNull(nameof(shouldRedirect), shouldRedirect) : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsFinalHandler => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the redirect URL.
|
||||
/// </summary>
|
||||
public string RedirectUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response status code.
|
||||
/// </summary>
|
||||
public HttpStatusCode StatusCode { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
if (_shouldRedirect?.Invoke(context) ?? true)
|
||||
{
|
||||
context.Redirect(RedirectUrl, (int)StatusCode);
|
||||
context.SetHandled();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Vendor/EmbedIO-3.5.2/Authentication/Auth.cs
vendored
Normal file
31
Vendor/EmbedIO-3.5.2/Authentication/Auth.cs
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace EmbedIO.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides useful authentication-related constants.
|
||||
/// </summary>
|
||||
public static class Auth
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IPrincipal"/> interface representing
|
||||
/// no user. To be used instead of <see langword="null"/>
|
||||
/// to initialize or set properties of type <see cref="IPrincipal"/>.
|
||||
/// </summary>
|
||||
public static IPrincipal NoUser { get; } = new GenericPrincipal(
|
||||
new GenericIdentity(string.Empty, string.Empty),
|
||||
null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns an <see cref="IPrincipal"/> interface
|
||||
/// representing an unauthenticated user, with the given
|
||||
/// authentication type.
|
||||
/// </summary>
|
||||
/// <param name="authenticationType">The type of authentication used to identify the user.</param>
|
||||
/// <returns>An <see cref="IPrincipal"/> interface.</returns>
|
||||
public static IPrincipal CreateUnauthenticatedPrincipal(string authenticationType)
|
||||
=> new GenericPrincipal(
|
||||
new GenericIdentity(string.Empty, authenticationType),
|
||||
null);
|
||||
}
|
||||
}
|
||||
46
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModule.cs
vendored
Normal file
46
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModule.cs
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple HTTP basic authentication module that stores credentials
|
||||
/// in a <seealso cref="ConcurrentDictionary{TKey,TValue}"/>.
|
||||
/// </summary>
|
||||
public class BasicAuthenticationModule : BasicAuthenticationModuleBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BasicAuthenticationModule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="realm">The authentication realm.</param>
|
||||
/// <remarks>
|
||||
/// <para>If <paramref name="realm"/> is <see langword="null"/> or the empty string,
|
||||
/// the <see cref="BasicAuthenticationModuleBase.Realm">Realm</see> property will be set equal to
|
||||
/// <see cref="IWebModule.BaseRoute">BaseRoute</see>.</para>
|
||||
/// </remarks>
|
||||
public BasicAuthenticationModule(string baseRoute, string? realm = null)
|
||||
: base(baseRoute, realm)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dictionary of valid user names and passwords.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The accounts.
|
||||
/// </value>
|
||||
public ConcurrentDictionary<string, string> Accounts { get; } = new ConcurrentDictionary<string, string>(StringComparer.InvariantCulture);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<bool> VerifyCredentialsAsync(string path, string userName, string password, CancellationToken cancellationToken)
|
||||
=> Task.FromResult(VerifyCredentialsInternal(userName, password));
|
||||
|
||||
private bool VerifyCredentialsInternal(string userName, string password)
|
||||
=> userName != null
|
||||
&& Accounts.TryGetValue(userName, out var storedPassword)
|
||||
&& string.Equals(password, storedPassword, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
103
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModuleBase.cs
vendored
Normal file
103
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModuleBase.cs
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements <see href="https://tools.ietf.org/html/rfc7617">HTTP basic authentication</see>.
|
||||
/// </summary>
|
||||
public abstract class BasicAuthenticationModuleBase : WebModuleBase
|
||||
{
|
||||
private readonly string _wwwAuthenticateHeaderValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BasicAuthenticationModuleBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base URL path.</param>
|
||||
/// <param name="realm">The authentication realm.</param>
|
||||
/// <remarks>
|
||||
/// <para>If <paramref name="realm"/> is <see langword="null"/> or the empty string,
|
||||
/// the <see cref="Realm"/> property will be set equal to
|
||||
/// <see cref="IWebModule.BaseRoute">BaseRoute</see>.</para>
|
||||
/// </remarks>
|
||||
protected BasicAuthenticationModuleBase(string baseRoute, string? realm)
|
||||
: base(baseRoute)
|
||||
{
|
||||
Realm = string.IsNullOrEmpty(realm) ? BaseRoute : realm;
|
||||
|
||||
_wwwAuthenticateHeaderValue = $"Basic realm=\"{Realm}\" charset=UTF-8";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsFinalHandler => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication realm.
|
||||
/// </summary>
|
||||
public string Realm { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected sealed override async Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
async Task<bool> IsAuthenticatedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var (userName, password) = GetCredentials(context.Request);
|
||||
return await VerifyCredentialsAsync(context.RequestedPath, userName, password, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// Credentials were not formatted correctly.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
context.Response.Headers.Set(HttpHeaderNames.WWWAuthenticate, _wwwAuthenticateHeaderValue);
|
||||
|
||||
if (!await IsAuthenticatedAsync().ConfigureAwait(false))
|
||||
throw HttpException.Unauthorized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the credentials given in the <c>Authentication</c> request header.
|
||||
/// </summary>
|
||||
/// <param name="path">The URL path requested by the client. Note that this is relative
|
||||
/// to the module's <see cref="WebModuleBase.BaseRoute">BaseRoute</see>.</param>
|
||||
/// <param name="userName">The user name, or <see langword="null" /> if none has been given.</param>
|
||||
/// <param name="password">The password, or <see langword="null" /> if none has been given.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken" /> use to cancel the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> whose result will be <see langword="true" /> if the given credentials
|
||||
/// are valid, <see langword="false" /> if they are not.</returns>
|
||||
protected abstract Task<bool> VerifyCredentialsAsync(string path, string userName, string password, CancellationToken cancellationToken);
|
||||
|
||||
private static (string UserName, string Password) GetCredentials(IHttpRequest request)
|
||||
{
|
||||
var authHeader = request.Headers[HttpHeaderNames.Authorization];
|
||||
|
||||
if (authHeader == null)
|
||||
return default;
|
||||
|
||||
if (!authHeader.StartsWith("basic ", StringComparison.OrdinalIgnoreCase))
|
||||
return default;
|
||||
|
||||
string credentials;
|
||||
try
|
||||
{
|
||||
credentials = WebServer.DefaultEncoding.GetString(Convert.FromBase64String(authHeader.Substring(6).Trim()));
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var separatorPos = credentials.IndexOf(':');
|
||||
return separatorPos < 0
|
||||
? (credentials, string.Empty)
|
||||
: (credentials.Substring(0, separatorPos), credentials.Substring(separatorPos + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModuleExtensions.cs
vendored
Normal file
34
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModuleExtensions.cs
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="BasicAuthenticationModule"/>.
|
||||
/// </summary>
|
||||
public static class BasicAuthenticationModuleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a username and password to the <see cref="BasicAuthenticationModule.Accounts">Accounts</see> dictionary.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="BasicAuthenticationModule"/> on which this method is called.</param>
|
||||
/// <param name="userName">The user name.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <returns><paramref name="this"/>, with the user name and password added.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="userName"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="OverflowException">
|
||||
/// <para>The <see cref="BasicAuthenticationModule.Accounts">Accounts</see> dictionary already contains
|
||||
/// the maximum number of elements (<see cref="int.MaxValue">MaxValue</see>).</para>
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>If a <paramref name="userName"/> account already exists,
|
||||
/// its password is replaced with <paramref name="password"/>.</para>
|
||||
/// </remarks>
|
||||
public static BasicAuthenticationModule WithAccount(this BasicAuthenticationModule @this, string userName, string password)
|
||||
{
|
||||
@this.Accounts.AddOrUpdate(userName, password, (_, __) => password);
|
||||
|
||||
return @this;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Vendor/EmbedIO-3.5.2/CompressionMethod.cs
vendored
Normal file
29
Vendor/EmbedIO-3.5.2/CompressionMethod.cs
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the compression method used to compress a message on
|
||||
/// the WebSocket connection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The compression methods that can be used are defined in
|
||||
/// <see href="https://tools.ietf.org/html/rfc7692">
|
||||
/// Compression Extensions for WebSocket</see>.
|
||||
/// </remarks>
|
||||
public enum CompressionMethod : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies no compression.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies "Deflate" compression.
|
||||
/// </summary>
|
||||
Deflate,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies GZip compression.
|
||||
/// </summary>
|
||||
Gzip,
|
||||
}
|
||||
}
|
||||
27
Vendor/EmbedIO-3.5.2/CompressionMethodNames.cs
vendored
Normal file
27
Vendor/EmbedIO-3.5.2/CompressionMethodNames.cs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes constants for possible values of the <c>Content-Encoding</c> HTTP header.
|
||||
/// </summary>
|
||||
/// <see cref="CompressionMethod"/>
|
||||
public static class CompressionMethodNames
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies no compression.
|
||||
/// </summary>
|
||||
/// <see cref="CompressionMethod.None"/>
|
||||
public const string None = "identity";
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the "Deflate" compression method.
|
||||
/// </summary>
|
||||
/// <see cref="CompressionMethod.Deflate"/>
|
||||
public const string Deflate = "deflate";
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the GZip compression method.
|
||||
/// </summary>
|
||||
/// <see cref="CompressionMethod.Gzip"/>
|
||||
public const string Gzip = "gzip";
|
||||
}
|
||||
}
|
||||
130
Vendor/EmbedIO-3.5.2/Cors/CorsModule.cs
vendored
Normal file
130
Vendor/EmbedIO-3.5.2/Cors/CorsModule.cs
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Cors
|
||||
{
|
||||
/// <summary>
|
||||
/// Cross-origin resource sharing (CORS) control Module.
|
||||
/// CORS is a mechanism that allows restricted resources (e.g. fonts)
|
||||
/// on a web page to be requested from another domain outside the domain from which the resource originated.
|
||||
/// </summary>
|
||||
public class CorsModule : WebModuleBase
|
||||
{
|
||||
/// <summary>
|
||||
/// A string meaning "All" in CORS headers.
|
||||
/// </summary>
|
||||
public const string All = "*";
|
||||
|
||||
private readonly string _origins;
|
||||
private readonly string _headers;
|
||||
private readonly string _methods;
|
||||
private readonly string[] _validOrigins;
|
||||
private readonly string[] _validMethods;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CorsModule" /> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="origins">The valid origins. The default is <see cref="All"/> (<c>*</c>).</param>
|
||||
/// <param name="headers">The valid headers. The default is <see cref="All"/> (<c>*</c>).</param>
|
||||
/// <param name="methods">The valid methods. The default is <see cref="All"/> (<c>*</c>).</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// origins
|
||||
/// or
|
||||
/// headers
|
||||
/// or
|
||||
/// methods
|
||||
/// </exception>
|
||||
public CorsModule(
|
||||
string baseRoute,
|
||||
string origins = All,
|
||||
string headers = All,
|
||||
string methods = All)
|
||||
: base(baseRoute)
|
||||
{
|
||||
_origins = origins ?? throw new ArgumentNullException(nameof(origins));
|
||||
_headers = headers ?? throw new ArgumentNullException(nameof(headers));
|
||||
_methods = methods ?? throw new ArgumentNullException(nameof(methods));
|
||||
|
||||
_validOrigins =
|
||||
origins.ToLowerInvariant()
|
||||
.SplitByComma(StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.ToArray();
|
||||
_validMethods =
|
||||
methods.ToLowerInvariant()
|
||||
.SplitByComma(StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsFinalHandler => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
var isOptions = context.Request.HttpVerb == HttpVerbs.Options;
|
||||
|
||||
// If we allow all we don't need to filter
|
||||
if (_origins == All && _headers == All && _methods == All)
|
||||
{
|
||||
context.Response.Headers.Set(HttpHeaderNames.AccessControlAllowOrigin, All);
|
||||
|
||||
if (isOptions)
|
||||
{
|
||||
ValidateHttpOptions(context);
|
||||
context.SetHandled();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var currentOrigin = context.Request.Headers[HttpHeaderNames.Origin];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(currentOrigin) && context.Request.IsLocal)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (_origins == All)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (_validOrigins.Contains(currentOrigin))
|
||||
{
|
||||
context.Response.Headers.Set(HttpHeaderNames.AccessControlAllowOrigin, currentOrigin);
|
||||
|
||||
if (isOptions)
|
||||
{
|
||||
ValidateHttpOptions(context);
|
||||
context.SetHandled();
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void ValidateHttpOptions(IHttpContext context)
|
||||
{
|
||||
var requestHeadersHeader = context.Request.Headers[HttpHeaderNames.AccessControlRequestHeaders];
|
||||
if (!string.IsNullOrWhiteSpace(requestHeadersHeader))
|
||||
{
|
||||
// TODO: Remove unwanted headers from request
|
||||
context.Response.Headers.Set(HttpHeaderNames.AccessControlAllowHeaders, requestHeadersHeader);
|
||||
}
|
||||
|
||||
var requestMethodHeader = context.Request.Headers[HttpHeaderNames.AccessControlRequestMethod];
|
||||
if (string.IsNullOrWhiteSpace(requestMethodHeader))
|
||||
return;
|
||||
|
||||
var currentMethods = requestMethodHeader.ToLowerInvariant()
|
||||
.SplitByComma(StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim());
|
||||
|
||||
if (_methods != All && !currentMethods.Any(_validMethods.Contains))
|
||||
throw HttpException.BadRequest();
|
||||
|
||||
context.Response.Headers.Set(HttpHeaderNames.AccessControlAllowMethods, requestMethodHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Vendor/EmbedIO-3.5.2/EmbedIOInternalErrorException.cs
vendored
Normal file
64
Vendor/EmbedIO-3.5.2/EmbedIOInternalErrorException.cs
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
/*
|
||||
* NOTE TO CONTRIBUTORS:
|
||||
*
|
||||
* Never use this exception directly.
|
||||
* Use the methods in EmbedIO.Internal.SelfCheck instead.
|
||||
*/
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
#pragma warning disable SA1642 // Constructor summary documentation should begin with standard text
|
||||
/// <summary>
|
||||
/// <para>The exception that is thrown by EmbedIO's internal diagnostic checks to signal a condition
|
||||
/// most probably caused by an error in EmbedIO.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class EmbedIOInternalErrorException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Initializes a new instance of the <see cref="EmbedIOInternalErrorException"/> class.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
public EmbedIOInternalErrorException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Initializes a new instance of the <see cref="EmbedIOInternalErrorException"/> class.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public EmbedIOInternalErrorException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Initializes a new instance of the <see cref="EmbedIOInternalErrorException"/> class.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception,
|
||||
/// or <see langword="null"/> if no inner exception is specified.</param>
|
||||
public EmbedIOInternalErrorException(string message, Exception? innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmbedIOInternalErrorException"/> class.
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="SerializationInfo"></see> that holds the serialized object data about the exception being thrown.</param>
|
||||
/// <param name="context">The <see cref="StreamingContext"></see> that contains contextual information about the source or destination.</param>
|
||||
protected EmbedIOInternalErrorException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
#pragma warning restore SA1642
|
||||
}
|
||||
165
Vendor/EmbedIO-3.5.2/ExceptionHandler.cs
vendored
Normal file
165
Vendor/EmbedIO-3.5.2/ExceptionHandler.cs
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Util;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides standard handlers for unhandled exceptions at both module and server level.
|
||||
/// </summary>
|
||||
/// <seealso cref="IWebServer.OnUnhandledException"/>
|
||||
/// <seealso cref="IWebModule.OnUnhandledException"/>
|
||||
public static class ExceptionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the response header used by the <see cref="EmptyResponseWithHeaders" />
|
||||
/// handler to transmit the type of the exception to the client.
|
||||
/// </summary>
|
||||
public const string ExceptionTypeHeaderName = "X-Exception-Type";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the response header used by the <see cref="EmptyResponseWithHeaders" />
|
||||
/// handler to transmit the message of the exception to the client.
|
||||
/// </summary>
|
||||
public const string ExceptionMessageHeaderName = "X-Exception-Message";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the contact information to include in exception responses.
|
||||
/// </summary>
|
||||
public static string? ContactInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to include stack traces
|
||||
/// in exception responses.
|
||||
/// </summary>
|
||||
public static bool IncludeStackTraces { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets the default handler used by <see cref="WebServerBase{TOptions}"/>.</para>
|
||||
/// <para>This is the same as <see cref="HtmlResponse"/>.</para>
|
||||
/// </summary>
|
||||
public static ExceptionHandlerCallback Default { get; } = HtmlResponse;
|
||||
|
||||
/// <summary>
|
||||
/// Sends an empty <c>500 Internal Server Error</c> response.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="exception">The unhandled exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
#pragma warning disable CA1801 // Unused parameter
|
||||
public static Task EmptyResponse(IHttpContext context, Exception exception)
|
||||
#pragma warning restore CA1801
|
||||
{
|
||||
context.Response.SetEmptyResponse((int)HttpStatusCode.InternalServerError);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Sends an empty <c>500 Internal Server Error</c> response,
|
||||
/// with the following additional headers:</para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Header</term>
|
||||
/// <description>Value</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term><c>X-Exception-Type</c></term>
|
||||
/// <description>The name (without namespace) of the type of exception that was thrown.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><c>X-Exception-Message</c></term>
|
||||
/// <description>The <see cref="Exception.Message">Message</see> property of the exception.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// <para>The aforementioned header names are available as the <see cref="ExceptionTypeHeaderName" /> and
|
||||
/// <see cref="ExceptionMessageHeaderName" /> properties, respectively.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="exception">The unhandled exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
public static Task EmptyResponseWithHeaders(IHttpContext context, Exception exception)
|
||||
{
|
||||
context.Response.SetEmptyResponse((int)HttpStatusCode.InternalServerError);
|
||||
context.Response.Headers[ExceptionTypeHeaderName] = Uri.EscapeDataString(exception.GetType().Name);
|
||||
context.Response.Headers[ExceptionMessageHeaderName] = Uri.EscapeDataString(exception.Message);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a <c>500 Internal Server Error</c> response with a HTML payload
|
||||
/// briefly describing the error, including contact information and/or a stack trace
|
||||
/// if specified via the <see cref="ContactInformation"/> and <see cref="IncludeStackTraces"/>
|
||||
/// properties, respectively.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="exception">The unhandled exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
public static Task HtmlResponse(IHttpContext context, Exception exception)
|
||||
=> context.SendStandardHtmlAsync(
|
||||
(int)HttpStatusCode.InternalServerError,
|
||||
text =>
|
||||
{
|
||||
text.Write("<p>The server has encountered an error and was not able to process your request.</p>");
|
||||
text.Write("<p>Please contact the server administrator");
|
||||
|
||||
if (!string.IsNullOrEmpty(ContactInformation))
|
||||
text.Write(" ({0})", WebUtility.HtmlEncode(ContactInformation));
|
||||
|
||||
text.Write(", informing them of the time this error occurred and the action(s) you performed that resulted in this error.</p>");
|
||||
text.Write("<p>The following information may help them in finding out what happened and restoring full functionality.</p>");
|
||||
text.Write(
|
||||
"<p><strong>Exception type:</strong> {0}<p><strong>Message:</strong> {1}",
|
||||
WebUtility.HtmlEncode(exception.GetType().FullName ?? "<unknown>"),
|
||||
WebUtility.HtmlEncode(exception.Message));
|
||||
|
||||
if (IncludeStackTraces)
|
||||
{
|
||||
text.Write(
|
||||
"</p><p><strong>Stack trace:</strong></p><br><pre>{0}</pre>",
|
||||
WebUtility.HtmlEncode(exception.StackTrace));
|
||||
}
|
||||
});
|
||||
|
||||
internal static async Task Handle(string logSource, IHttpContext context, Exception exception, ExceptionHandlerCallback? handler, HttpExceptionHandlerCallback? httpHandler)
|
||||
{
|
||||
if (handler == null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(exception).Throw();
|
||||
return;
|
||||
}
|
||||
|
||||
exception.Log(logSource, $"[{context.Id}] Unhandled exception.");
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.SetEmptyResponse((int)HttpStatusCode.InternalServerError);
|
||||
context.Response.DisableCaching();
|
||||
await handler(context, exception)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (context.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception httpException) when (httpException is IHttpException httpException1)
|
||||
{
|
||||
if (httpHandler == null)
|
||||
throw;
|
||||
|
||||
await httpHandler(context, httpException1).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception2)
|
||||
{
|
||||
exception2.Log(logSource, $"[{context.Id}] Unhandled exception while handling exception.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Vendor/EmbedIO-3.5.2/ExceptionHandlerCallback.cs
vendored
Normal file
22
Vendor/EmbedIO-3.5.2/ExceptionHandlerCallback.cs
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// A callback used to provide information about an unhandled exception occurred while processing a request.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="exception">The unhandled exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
/// <remarks>
|
||||
/// <para>When this delegate is called, the response's status code has already been set to
|
||||
/// <see cref="HttpStatusCode.InternalServerError" />.</para>
|
||||
/// <para>Any exception thrown by a handler (even a HTTP exception) will go unhandled: the web server
|
||||
/// will not crash, but processing of the request will be aborted, and the response will be flushed as-is.
|
||||
/// In other words, it is not a good ides to <c>throw HttpException.NotFound()</c> (or similar)
|
||||
/// from a handler.</para>
|
||||
/// </remarks>
|
||||
public delegate Task ExceptionHandlerCallback(IHttpContext context, Exception exception);
|
||||
}
|
||||
20
Vendor/EmbedIO-3.5.2/Files/DirectoryLister.cs
vendored
Normal file
20
Vendor/EmbedIO-3.5.2/Files/DirectoryLister.cs
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
using EmbedIO.Files.Internal;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides standard directory listers for <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDirectoryLister"/>
|
||||
public static class DirectoryLister
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Gets an <see cref="IDirectoryLister"/> interface
|
||||
/// that produces a HTML listing of a directory.</para>
|
||||
/// <para>The output of the returned directory lister
|
||||
/// is the same as a directory listing obtained
|
||||
/// by EmbedIO version 2.</para>
|
||||
/// </summary>
|
||||
public static IDirectoryLister Html => HtmlDirectoryLister.Instance;
|
||||
}
|
||||
}
|
||||
185
Vendor/EmbedIO-3.5.2/Files/FileCache.Section.cs
vendored
Normal file
185
Vendor/EmbedIO-3.5.2/Files/FileCache.Section.cs
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using EmbedIO.Files.Internal;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
public sealed partial class FileCache
|
||||
{
|
||||
internal class Section
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly Dictionary<string, FileCacheItem> _items = new Dictionary<string, FileCacheItem>(StringComparer.Ordinal);
|
||||
private long _totalSize;
|
||||
private string? _oldestKey;
|
||||
private string? _newestKey;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
ClearCore();
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(string path, FileCacheItem item)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
AddItemCore(path, item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(string path)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
RemoveItemCore(path);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGet(string path, out FileCacheItem item)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (!_items.TryGetValue(path, out item))
|
||||
return false;
|
||||
|
||||
RefreshItemCore(path, item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal long GetLeastRecentUseTime()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _oldestKey == null ? long.MaxValue : _items[_oldestKey].LastUsedAt;
|
||||
}
|
||||
}
|
||||
|
||||
// Removes least recently used item.
|
||||
// Returns size of removed item.
|
||||
internal long RemoveLeastRecentItem()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return RemoveLeastRecentItemCore();
|
||||
}
|
||||
}
|
||||
|
||||
internal long GetTotalSize()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _totalSize;
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateTotalSize(long delta)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_totalSize += delta;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearCore()
|
||||
{
|
||||
_items.Clear();
|
||||
_totalSize = 0;
|
||||
_oldestKey = null;
|
||||
_newestKey = null;
|
||||
}
|
||||
|
||||
// Adds an item as most recently used.
|
||||
private void AddItemCore(string path, FileCacheItem item)
|
||||
{
|
||||
item.PreviousKey = _newestKey;
|
||||
item.NextKey = null;
|
||||
item.LastUsedAt = TimeBase.ElapsedTicks;
|
||||
|
||||
if (_newestKey != null)
|
||||
_items[_newestKey].NextKey = path;
|
||||
|
||||
_newestKey = path;
|
||||
|
||||
_items[path] = item;
|
||||
_totalSize += item.SizeInCache;
|
||||
}
|
||||
|
||||
// Removes an item.
|
||||
private void RemoveItemCore(string path)
|
||||
{
|
||||
if (!_items.TryGetValue(path, out var item))
|
||||
return;
|
||||
|
||||
if (_oldestKey == path)
|
||||
_oldestKey = item.NextKey;
|
||||
|
||||
if (_newestKey == path)
|
||||
_newestKey = item.PreviousKey;
|
||||
|
||||
if (item.PreviousKey != null)
|
||||
_items[item.PreviousKey].NextKey = item.NextKey;
|
||||
|
||||
if (item.NextKey != null)
|
||||
_items[item.NextKey].PreviousKey = item.PreviousKey;
|
||||
|
||||
item.PreviousKey = null;
|
||||
item.NextKey = null;
|
||||
|
||||
_items.Remove(path);
|
||||
_totalSize -= item.SizeInCache;
|
||||
}
|
||||
|
||||
// Removes the least recently used item.
|
||||
// returns size of removed item.
|
||||
private long RemoveLeastRecentItemCore()
|
||||
{
|
||||
var path = _oldestKey;
|
||||
if (path == null)
|
||||
return 0;
|
||||
|
||||
var item = _items[path];
|
||||
|
||||
if ((_oldestKey = item.NextKey) != null)
|
||||
_items[_oldestKey].PreviousKey = null;
|
||||
|
||||
if (_newestKey == path)
|
||||
_newestKey = null;
|
||||
|
||||
item.PreviousKey = null;
|
||||
item.NextKey = null;
|
||||
|
||||
_items.Remove(path);
|
||||
_totalSize -= item.SizeInCache;
|
||||
return item.SizeInCache;
|
||||
}
|
||||
|
||||
// Moves an item to most recently used.
|
||||
private void RefreshItemCore(string path, FileCacheItem item)
|
||||
{
|
||||
item.LastUsedAt = TimeBase.ElapsedTicks;
|
||||
|
||||
if (_newestKey == path)
|
||||
return;
|
||||
|
||||
if (_oldestKey == path)
|
||||
_oldestKey = item.NextKey;
|
||||
|
||||
if (item.PreviousKey != null)
|
||||
_items[item.PreviousKey].NextKey = item.NextKey;
|
||||
|
||||
if (item.NextKey != null)
|
||||
_items[item.NextKey].PreviousKey = item.PreviousKey;
|
||||
|
||||
item.PreviousKey = _newestKey;
|
||||
item.NextKey = null;
|
||||
|
||||
_items[_newestKey!].NextKey = path;
|
||||
_newestKey = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
178
Vendor/EmbedIO-3.5.2/Files/FileCache.cs
vendored
Normal file
178
Vendor/EmbedIO-3.5.2/Files/FileCache.cs
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Internal;
|
||||
using Swan.Threading;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
#pragma warning disable CA1001 // Type owns disposable field '_cleaner' but is not disposable - _cleaner has its own dispose semantics.
|
||||
/// <summary>
|
||||
/// A cache where one or more instances of <see cref="FileModule"/> can store hashes and file contents.
|
||||
/// </summary>
|
||||
public sealed partial class FileCache
|
||||
#pragma warning restore CA1001
|
||||
{
|
||||
/// <summary>
|
||||
/// The default value for the <see cref="MaxSizeKb"/> property.
|
||||
/// </summary>
|
||||
public const int DefaultMaxSizeKb = 10240;
|
||||
|
||||
/// <summary>
|
||||
/// The default value for the <see cref="MaxFileSizeKb"/> property.
|
||||
/// </summary>
|
||||
public const int DefaultMaxFileSizeKb = 200;
|
||||
|
||||
private static readonly Stopwatch TimeBase = Stopwatch.StartNew();
|
||||
|
||||
private static readonly object DefaultSyncRoot = new object();
|
||||
private static FileCache? _defaultInstance;
|
||||
|
||||
private readonly ConcurrentDictionary<string, Section> _sections = new ConcurrentDictionary<string, Section>(StringComparer.Ordinal);
|
||||
private int _sectionCount; // Because ConcurrentDictionary<,>.Count is locking.
|
||||
private int _maxSizeKb = DefaultMaxSizeKb;
|
||||
private int _maxFileSizeKb = DefaultMaxFileSizeKb;
|
||||
private PeriodicTask? _cleaner;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default <see cref="FileCache"/> instance used by <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
public static FileCache Default
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_defaultInstance != null)
|
||||
return _defaultInstance;
|
||||
|
||||
lock (DefaultSyncRoot)
|
||||
{
|
||||
if (_defaultInstance == null)
|
||||
_defaultInstance = new FileCache();
|
||||
}
|
||||
|
||||
return _defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the maximum total size of cached data in kilobytes (1 kilobyte = 1024 bytes).</para>
|
||||
/// <para>The default value for this property is stored in the <see cref="DefaultMaxSizeKb"/> constant field.</para>
|
||||
/// <para>Setting this property to a value less lower han 1 has the same effect as setting it to 1.</para>
|
||||
/// </summary>
|
||||
public int MaxSizeKb
|
||||
{
|
||||
get => _maxSizeKb;
|
||||
set => _maxSizeKb = Math.Max(value, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the maximum size of a single cached file in kilobytes (1 kilobyte = 1024 bytes).</para>
|
||||
/// <para>A single file's contents may be present in a cache more than once, if the file
|
||||
/// is requested with different <c>Accept-Encoding</c> request headers. This property acts as a threshold
|
||||
/// for the uncompressed size of a file.</para>
|
||||
/// <para>The default value for this property is stored in the <see cref="DefaultMaxFileSizeKb"/> constant field.</para>
|
||||
/// <para>Setting this property to a value lower than 0 has the same effect as setting it to 0, in fact
|
||||
/// completely disabling the caching of file contents for this cache.</para>
|
||||
/// <para>This property cannot be set to a value higher than 2097151; in other words, it is not possible
|
||||
/// to cache files bigger than two Gigabytes (1 Gigabyte = 1048576 kilobytes) minus 1 kilobyte.</para>
|
||||
/// </summary>
|
||||
public int MaxFileSizeKb
|
||||
{
|
||||
get => _maxFileSizeKb;
|
||||
set => _maxFileSizeKb = Math.Min(Math.Max(value, 0), 2097151);
|
||||
}
|
||||
|
||||
// Cast as IDictionary because we WANT an exception to be thrown if the name exists.
|
||||
// It would mean that something is very, very wrong.
|
||||
internal Section AddSection(string name)
|
||||
{
|
||||
var section = new Section();
|
||||
(_sections as IDictionary<string, Section>).Add(name, section);
|
||||
|
||||
if (Interlocked.Increment(ref _sectionCount) == 1)
|
||||
_cleaner = new PeriodicTask(TimeSpan.FromMinutes(1), CheckMaxSize);
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
internal void RemoveSection(string name)
|
||||
{
|
||||
_sections.TryRemove(name, out _);
|
||||
|
||||
if (Interlocked.Decrement(ref _sectionCount) == 0)
|
||||
{
|
||||
_cleaner?.Dispose();
|
||||
_cleaner = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckMaxSize(CancellationToken cancellationToken)
|
||||
{
|
||||
var timeKeeper = new TimeKeeper();
|
||||
var maxSizeKb = _maxSizeKb;
|
||||
var initialSizeKb = ComputeTotalSize() / 1024L;
|
||||
|
||||
if (initialSizeKb <= maxSizeKb)
|
||||
{
|
||||
$"Total size = {initialSizeKb}/{_maxSizeKb}kb, not purging.".Debug(nameof(FileCache));
|
||||
return;
|
||||
}
|
||||
|
||||
$"Total size = {initialSizeKb}/{_maxSizeKb}kb, purging...".Debug(nameof(FileCache));
|
||||
|
||||
var removedCount = 0;
|
||||
var removedSize = 0L;
|
||||
var totalSizeKb = initialSizeKb;
|
||||
var threshold = 973L * maxSizeKb / 1024L; // About 95% of maximum allowed size
|
||||
while (totalSizeKb > threshold)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
var section = GetSectionWithLeastRecentItem();
|
||||
if (section == null)
|
||||
return;
|
||||
|
||||
removedSize += section.RemoveLeastRecentItem();
|
||||
removedCount++;
|
||||
|
||||
await Task.Yield();
|
||||
|
||||
totalSizeKb = ComputeTotalSize() / 1024L;
|
||||
}
|
||||
|
||||
$"Purge completed in {timeKeeper.ElapsedTime}ms: removed {removedCount} items ({removedSize / 1024L}kb). Total size is now {totalSizeKb}kb."
|
||||
.Debug(nameof(FileCache));
|
||||
}
|
||||
|
||||
// Enumerate key / value pairs because the Keys and Values property
|
||||
// of ConcurrentDictionary<,> have snapshot semantics,
|
||||
// while GetEnumerator enumerates without locking.
|
||||
private long ComputeTotalSize()
|
||||
=> _sections.Sum(pair => pair.Value.GetTotalSize());
|
||||
|
||||
private Section? GetSectionWithLeastRecentItem()
|
||||
{
|
||||
Section? result = null;
|
||||
var earliestTime = long.MaxValue;
|
||||
foreach (var pair in _sections)
|
||||
{
|
||||
var section = pair.Value;
|
||||
var time = section.GetLeastRecentUseTime();
|
||||
|
||||
if (time < earliestTime)
|
||||
{
|
||||
result = section;
|
||||
earliestTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
635
Vendor/EmbedIO-3.5.2/Files/FileModule.cs
vendored
Normal file
635
Vendor/EmbedIO-3.5.2/Files/FileModule.cs
vendored
Normal file
@@ -0,0 +1,635 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Files.Internal;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// A module serving files and directory listings from an <see cref="IFileProvider"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="WebModuleBase" />
|
||||
public class FileModule : WebModuleBase, IDisposable, IMimeTypeCustomizer
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Default value for <see cref="DefaultDocument"/>.</para>
|
||||
/// </summary>
|
||||
public const string DefaultDocumentName = "index.html";
|
||||
|
||||
private readonly string _cacheSectionName = UniqueIdGenerator.GetNext();
|
||||
private readonly MimeTypeCustomizer _mimeTypeCustomizer = new MimeTypeCustomizer();
|
||||
private readonly ConcurrentDictionary<string, MappedResourceInfo>? _mappingCache;
|
||||
|
||||
private FileCache _cache = FileCache.Default;
|
||||
private bool _contentCaching = true;
|
||||
private string? _defaultDocument = DefaultDocumentName;
|
||||
private string? _defaultExtension;
|
||||
private IDirectoryLister? _directoryLister;
|
||||
private FileRequestHandlerCallback _onMappingFailed = FileRequestHandler.ThrowNotFound;
|
||||
private FileRequestHandlerCallback _onDirectoryNotListable = FileRequestHandler.ThrowUnauthorized;
|
||||
private FileRequestHandlerCallback _onMethodNotAllowed = FileRequestHandler.ThrowMethodNotAllowed;
|
||||
|
||||
private FileCache.Section? _cacheSection;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileModule"/> class,
|
||||
/// using the specified cache.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="provider">An <see cref="IFileProvider"/> interface that provides access
|
||||
/// to actual files and directories.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="provider"/> is <see langword="null"/>.</exception>
|
||||
public FileModule(string baseRoute, IFileProvider provider)
|
||||
: base(baseRoute)
|
||||
{
|
||||
Provider = Validate.NotNull(nameof(provider), provider);
|
||||
_mappingCache = Provider.IsImmutable
|
||||
? new ConcurrentDictionary<string, MappedResourceInfo>()
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="FileModule"/> class.
|
||||
/// </summary>
|
||||
~FileModule()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsFinalHandler => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IFileProvider"/>interface that provides access
|
||||
/// to actual files and directories served by this module.
|
||||
/// </summary>
|
||||
public IFileProvider Provider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="FileCache"/> used by this module to store hashes and,
|
||||
/// optionally, file contents and rendered directory listings.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
|
||||
public FileCache Cache
|
||||
{
|
||||
get => _cache;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_cache = Validate.NotNull(nameof(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a value indicating whether this module caches the contents of files
|
||||
/// and directory listings.</para>
|
||||
/// <para>Note that the actual representations of files are stored in <see cref="FileCache"/>;
|
||||
/// thus, for example, if a file is always requested with an <c>Accept-Encoding</c> of <c>gzip</c>,
|
||||
/// only the gzipped contents of the file will be cached.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
public bool ContentCaching
|
||||
{
|
||||
get => _contentCaching;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_contentCaching = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the name of the default document served, if it exists, instead of a directory listing
|
||||
/// when the path of a requested URL maps to a directory.</para>
|
||||
/// <para>The default value for this property is the <see cref="DefaultDocumentName"/> constant.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
public string? DefaultDocument
|
||||
{
|
||||
get => _defaultDocument;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_defaultDocument = string.IsNullOrEmpty(value) ? null : value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the default extension appended to requested URL paths that do not map
|
||||
/// to any file or directory. Defaults to <see langword="null"/>.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
/// <exception cref="ArgumentException">This property is being set to a non-<see langword="null"/>,
|
||||
/// non-empty string that does not start with a period (<c>.</c>).</exception>
|
||||
public string? DefaultExtension
|
||||
{
|
||||
get => _defaultExtension;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_defaultExtension = null;
|
||||
}
|
||||
else if (value![0] != '.')
|
||||
{
|
||||
throw new ArgumentException("Default extension does not start with a period.", nameof(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
_defaultExtension = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the <see cref="IDirectoryLister"/> interface used to generate
|
||||
/// directory listing in this module.</para>
|
||||
/// <para>A value of <see langword="null"/> (the default) disables the generation
|
||||
/// of directory listings.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
public IDirectoryLister? DirectoryLister
|
||||
{
|
||||
get => _directoryLister;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_directoryLister = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a <see cref="FileRequestHandlerCallback"/> that is called whenever
|
||||
/// the requested URL path could not be mapped to any file or directory.</para>
|
||||
/// <para>The default is <see cref="FileRequestHandler.ThrowNotFound"/>.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public FileRequestHandlerCallback OnMappingFailed
|
||||
{
|
||||
get => _onMappingFailed;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_onMappingFailed = Validate.NotNull(nameof(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a <see cref="FileRequestHandlerCallback"/> that is called whenever
|
||||
/// the requested URL path has been mapped to a directory, but directory listing has been
|
||||
/// disabled by setting <see cref="DirectoryLister"/> to <see langword="null"/>.</para>
|
||||
/// <para>The default is <see cref="FileRequestHandler.ThrowUnauthorized"/>.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public FileRequestHandlerCallback OnDirectoryNotListable
|
||||
{
|
||||
get => _onDirectoryNotListable;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_onDirectoryNotListable = Validate.NotNull(nameof(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a <see cref="FileRequestHandlerCallback"/> that is called whenever
|
||||
/// the requested URL path has been mapped to a file or directory, but the request's
|
||||
/// HTTP method is neither <c>GET</c> nor <c>HEAD</c>.</para>
|
||||
/// <para>The default is <see cref="FileRequestHandler.ThrowMethodNotAllowed"/>.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public FileRequestHandlerCallback OnMethodNotAllowed
|
||||
{
|
||||
get => _onMethodNotAllowed;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_onMethodNotAllowed = Validate.NotNull(nameof(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
string IMimeTypeProvider.GetMimeType(string extension)
|
||||
=> _mimeTypeCustomizer.GetMimeType(extension);
|
||||
|
||||
bool IMimeTypeProvider.TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
=> _mimeTypeCustomizer.TryDetermineCompression(mimeType, out preferCompression);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddCustomMimeType(string extension, string mimeType)
|
||||
=> _mimeTypeCustomizer.AddCustomMimeType(extension, mimeType);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PreferCompression(string mimeType, bool preferCompression)
|
||||
=> _mimeTypeCustomizer.PreferCompression(mimeType, preferCompression);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the part of <see cref="Cache"/> used by this module.
|
||||
/// </summary>
|
||||
public void ClearCache()
|
||||
{
|
||||
_mappingCache?.Clear();
|
||||
_cacheSection?.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources;
|
||||
/// <see langword="false"/> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (_cacheSection != null)
|
||||
Provider.ResourceChanged -= _cacheSection.Remove;
|
||||
|
||||
if (Provider is IDisposable disposableProvider)
|
||||
disposableProvider.Dispose();
|
||||
|
||||
if (_cacheSection != null)
|
||||
Cache.RemoveSection(_cacheSectionName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnBeforeLockConfiguration()
|
||||
{
|
||||
base.OnBeforeLockConfiguration();
|
||||
|
||||
_mimeTypeCustomizer.Lock();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnStart(CancellationToken cancellationToken)
|
||||
{
|
||||
base.OnStart(cancellationToken);
|
||||
|
||||
_cacheSection = Cache.AddSection(_cacheSectionName);
|
||||
Provider.ResourceChanged += _cacheSection.Remove;
|
||||
Provider.Start(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
MappedResourceInfo? info;
|
||||
|
||||
var path = context.RequestedPath;
|
||||
|
||||
// Map the URL path to a mapped resource.
|
||||
// DefaultDocument and DefaultExtension are handled here.
|
||||
// Use the mapping cache if it exists.
|
||||
if (_mappingCache == null)
|
||||
{
|
||||
info = MapUrlPath(path, context);
|
||||
}
|
||||
else if (!_mappingCache.TryGetValue(path, out info))
|
||||
{
|
||||
info = MapUrlPath(path, context);
|
||||
if (info != null)
|
||||
_ = _mappingCache.AddOrUpdate(path, info, (_, __) => info);
|
||||
}
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
// If mapping failed, send a "404 Not Found" response, or whatever OnMappingFailed chooses to do.
|
||||
// For example, it may return a default resource (think a folder of images and an imageNotFound.jpg),
|
||||
// or redirect the request.
|
||||
await OnMappingFailed(context, null).ConfigureAwait(false);
|
||||
}
|
||||
else if (!IsHttpMethodAllowed(context.Request, out var sendResponseBody))
|
||||
{
|
||||
// If there is a mapped resource, check that the HTTP method is either GET or HEAD.
|
||||
// Otherwise, send a "405 Method Not Allowed" response, or whatever OnMethodNotAllowed chooses to do.
|
||||
await OnMethodNotAllowed(context, info).ConfigureAwait(false);
|
||||
}
|
||||
else if (info.IsDirectory && DirectoryLister == null)
|
||||
{
|
||||
// If a directory listing was requested, but there is no DirectoryLister,
|
||||
// send a "403 Unauthorized" response, or whatever OnDirectoryNotListable chooses to do.
|
||||
// For example, one could prefer to send "404 Not Found" instead.
|
||||
await OnDirectoryNotListable(context, info).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await HandleResource(context, info, sendResponseBody).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Tells whether a request's HTTP method is suitable for processing by FileModule
|
||||
// and, if so, whether a response body must be sent.
|
||||
private static bool IsHttpMethodAllowed(IHttpRequest request, out bool sendResponseBody)
|
||||
{
|
||||
switch (request.HttpVerb)
|
||||
{
|
||||
case HttpVerbs.Head:
|
||||
sendResponseBody = false;
|
||||
return true;
|
||||
case HttpVerbs.Get:
|
||||
sendResponseBody = true;
|
||||
return true;
|
||||
default:
|
||||
sendResponseBody = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepares response headers for a "200 OK" or "304 Not Modified" response.
|
||||
// RFC7232, Section 4.1
|
||||
private static void PreparePositiveResponse(IHttpResponse response, MappedResourceInfo info, string contentType, string entityTag, Action<IHttpResponse> setCompression)
|
||||
{
|
||||
setCompression(response);
|
||||
response.ContentType = contentType;
|
||||
response.Headers.Set(HttpHeaderNames.ETag, entityTag);
|
||||
response.Headers.Set(HttpHeaderNames.LastModified, HttpDate.Format(info.LastModifiedUtc));
|
||||
response.Headers.Set(HttpHeaderNames.CacheControl, "max-age=0, must-revalidate");
|
||||
response.Headers.Set(HttpHeaderNames.AcceptRanges, "bytes");
|
||||
}
|
||||
|
||||
// Attempts to map a module-relative URL path to a mapped resource,
|
||||
// handling DefaultDocument and DefaultExtension.
|
||||
// Returns null if not found.
|
||||
// Directories mus be returned regardless of directory listing being enabled.
|
||||
private MappedResourceInfo? MapUrlPath(string urlPath, IMimeTypeProvider mimeTypeProvider)
|
||||
{
|
||||
var result = Provider.MapUrlPath(urlPath, mimeTypeProvider);
|
||||
|
||||
// If urlPath maps to a file, no further searching is needed.
|
||||
if (result?.IsFile ?? false)
|
||||
return result;
|
||||
|
||||
// Look for a default document.
|
||||
// Don't append an additional slash if the URL path is "/".
|
||||
// The default document, if found, must be a file, not a directory.
|
||||
if (DefaultDocument != null)
|
||||
{
|
||||
var defaultDocumentPath = urlPath + (urlPath.Length > 1 ? "/" : string.Empty) + DefaultDocument;
|
||||
var defaultDocumentResult = Provider.MapUrlPath(defaultDocumentPath, mimeTypeProvider);
|
||||
if (defaultDocumentResult?.IsFile ?? false)
|
||||
return defaultDocumentResult;
|
||||
}
|
||||
|
||||
// Try to apply default extension (but not if the URL path is "/",
|
||||
// i.e. the only normalized, non-base URL path that ends in a slash).
|
||||
// When the default extension is applied, the result must be a file.
|
||||
if (DefaultExtension != null && urlPath.Length > 1)
|
||||
{
|
||||
var defaultExtensionResult = Provider.MapUrlPath(urlPath + DefaultExtension, mimeTypeProvider);
|
||||
if (defaultExtensionResult?.IsFile ?? false)
|
||||
return defaultExtensionResult;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task HandleResource(IHttpContext context, MappedResourceInfo info, bool sendResponseBody)
|
||||
{
|
||||
// Try to extract resource information from cache.
|
||||
var cachingThreshold = 1024L * Cache.MaxFileSizeKb;
|
||||
if (!_cacheSection!.TryGet(info.Path, out var cacheItem))
|
||||
{
|
||||
// Resource information not yet cached
|
||||
cacheItem = new FileCacheItem(_cacheSection, info.LastModifiedUtc, info.Length);
|
||||
_cacheSection.Add(info.Path, cacheItem);
|
||||
}
|
||||
else if (!Provider.IsImmutable)
|
||||
{
|
||||
// Check whether the resource has changed.
|
||||
// If so, discard the cache item and create a new one.
|
||||
if (cacheItem.LastModifiedUtc != info.LastModifiedUtc || cacheItem.Length != info.Length)
|
||||
{
|
||||
_cacheSection.Remove(info.Path);
|
||||
cacheItem = new FileCacheItem(_cacheSection, info.LastModifiedUtc, info.Length);
|
||||
_cacheSection.Add(info.Path, cacheItem);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now we have a cacheItem for the resource.
|
||||
* It may have been just created, or it may or may not have a cached content,
|
||||
* depending upon the value of the ContentCaching property,
|
||||
* the size of the resource, and the value of the
|
||||
* MaxFileSizeKb of our Cache.
|
||||
*/
|
||||
|
||||
// If the content type is not a valid MIME type, assume the default.
|
||||
var contentType = info.ContentType ?? DirectoryLister?.ContentType ?? MimeType.Default;
|
||||
var mimeType = MimeType.StripParameters(contentType);
|
||||
if (!MimeType.IsMimeType(mimeType, false))
|
||||
contentType = mimeType = MimeType.Default;
|
||||
|
||||
// Next we're going to apply proactive negotiation
|
||||
// to determine whether we agree with the client upon the compression
|
||||
// (or lack of it) to use for the resource.
|
||||
//
|
||||
// The combination of partial responses and entity compression
|
||||
// is not really standardized and could lead to a world of pain.
|
||||
// Thus, if there is a Range header in the request, try to negotiate for no compression.
|
||||
// Later, if there is compression anyway, we will ignore the Range header.
|
||||
if (!context.TryDetermineCompression(mimeType, out var preferCompression))
|
||||
preferCompression = true;
|
||||
preferCompression &= context.Request.Headers.Get(HttpHeaderNames.Range) == null;
|
||||
if (!context.Request.TryNegotiateContentEncoding(preferCompression, out var compressionMethod, out var setCompressionInResponse))
|
||||
{
|
||||
// If negotiation failed, the returned callback will do the right thing.
|
||||
setCompressionInResponse(context.Response);
|
||||
return;
|
||||
}
|
||||
|
||||
var entityTag = info.GetEntityTag(compressionMethod);
|
||||
|
||||
// Send a "304 Not Modified" response if applicable.
|
||||
//
|
||||
// RFC7232, Section 3.3: "A recipient MUST ignore If-Modified-Since
|
||||
// if the request contains an If-None-Match header field."
|
||||
if (context.Request.CheckIfNoneMatch(entityTag, out var ifNoneMatchExists)
|
||||
|| (!ifNoneMatchExists && context.Request.CheckIfModifiedSince(info.LastModifiedUtc, out _)))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotModified;
|
||||
PreparePositiveResponse(context.Response, info, contentType, entityTag, setCompressionInResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point we know the response is "200 OK",
|
||||
* unless the request is a range request.
|
||||
*
|
||||
* RFC7233, Section 3.1: "The Range header field is evaluated after evaluating the precondition
|
||||
* header fields defined in RFC7232, and only if the result in absence
|
||||
* of the Range header field would be a 200 (OK) response. In other
|
||||
* words, Range is ignored when a conditional GET would result in a 304
|
||||
* (Not Modified) response."
|
||||
*/
|
||||
|
||||
// Before evaluating ranges, we must know the content length.
|
||||
// This is easy for files, as it is stored in info.Length.
|
||||
// Directories always have info.Length == 0; therefore,
|
||||
// unless the directory listing is cached, we must generate it now
|
||||
// (and cache it while we're there, if applicable).
|
||||
var content = cacheItem.GetContent(compressionMethod);
|
||||
if (info.IsDirectory && content == null)
|
||||
{
|
||||
long uncompressedLength;
|
||||
(content, uncompressedLength) = await GenerateDirectoryListingAsync(context, info, compressionMethod)
|
||||
.ConfigureAwait(false);
|
||||
if (ContentCaching && uncompressedLength <= cachingThreshold)
|
||||
_ = cacheItem.SetContent(compressionMethod, content);
|
||||
}
|
||||
|
||||
var contentLength = content?.Length ?? info.Length;
|
||||
|
||||
// Ignore range request is compression is enabled
|
||||
// (or should I say forced, since negotiation has tried not to use it).
|
||||
var partialStart = 0L;
|
||||
var partialUpperBound = contentLength - 1;
|
||||
var isPartial = compressionMethod == CompressionMethod.None
|
||||
&& context.Request.IsRangeRequest(contentLength, entityTag, info.LastModifiedUtc, out partialStart, out partialUpperBound);
|
||||
var responseContentLength = contentLength;
|
||||
|
||||
if (isPartial)
|
||||
{
|
||||
// Prepare a "206 Partial Content" response.
|
||||
responseContentLength = partialUpperBound - partialStart + 1;
|
||||
context.Response.StatusCode = (int)HttpStatusCode.PartialContent;
|
||||
PreparePositiveResponse(context.Response, info, contentType, entityTag, setCompressionInResponse);
|
||||
context.Response.Headers.Set(HttpHeaderNames.ContentRange, $"bytes {partialStart}-{partialUpperBound}/{contentLength}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prepare a "200 OK" response.
|
||||
PreparePositiveResponse(context.Response, info, contentType, entityTag, setCompressionInResponse);
|
||||
}
|
||||
|
||||
// If it's a HEAD request, we're done.
|
||||
if (!sendResponseBody)
|
||||
return;
|
||||
|
||||
// If content must be sent AND cached, first read it and store it.
|
||||
// If the requested resource is a directory, we have already listed it by now,
|
||||
// so it must be a file for content to be null.
|
||||
if (content == null && ContentCaching && contentLength <= cachingThreshold)
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (var compressor = new CompressionStream(memoryStream, compressionMethod))
|
||||
{
|
||||
using var source = Provider.OpenFile(info.Path);
|
||||
await source.CopyToAsync(compressor, WebServer.StreamCopyBufferSize, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
content = memoryStream.ToArray();
|
||||
responseContentLength = content.Length;
|
||||
}
|
||||
|
||||
_ = cacheItem.SetContent(compressionMethod, content);
|
||||
}
|
||||
|
||||
// Transfer cached content if present.
|
||||
if (content != null)
|
||||
{
|
||||
context.Response.ContentLength64 = responseContentLength;
|
||||
var offset = isPartial ? (int) partialStart : 0;
|
||||
await context.Response.OutputStream.WriteAsync(content, offset, (int)responseContentLength, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Read and transfer content without caching.
|
||||
using (var source = Provider.OpenFile(info.Path))
|
||||
{
|
||||
context.Response.SendChunked = true;
|
||||
|
||||
if (isPartial)
|
||||
{
|
||||
var buffer = new byte[WebServer.StreamCopyBufferSize];
|
||||
if (source.CanSeek)
|
||||
{
|
||||
source.Position = partialStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
var skipLength = (int)partialStart;
|
||||
while (skipLength > 0)
|
||||
{
|
||||
var read = await source.ReadAsync(buffer, 0, Math.Min(skipLength, buffer.Length), context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
skipLength -= read;
|
||||
}
|
||||
}
|
||||
|
||||
var transferSize = responseContentLength;
|
||||
while (transferSize >= WebServer.StreamCopyBufferSize)
|
||||
{
|
||||
var read = await source.ReadAsync(buffer, 0, WebServer.StreamCopyBufferSize, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await context.Response.OutputStream.WriteAsync(buffer, 0, read, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
transferSize -= read;
|
||||
}
|
||||
|
||||
if (transferSize > 0)
|
||||
{
|
||||
var read = await source.ReadAsync(buffer, 0, (int)transferSize, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await context.Response.OutputStream.WriteAsync(buffer, 0, read, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using var compressor = new CompressionStream(context.Response.OutputStream, compressionMethod);
|
||||
await source.CopyToAsync(compressor, WebServer.StreamCopyBufferSize, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Uses DirectoryLister to generate a directory listing asynchronously.
|
||||
// Returns a tuple of the generated content and its *uncompressed* length
|
||||
// (useful to decide whether it can be cached).
|
||||
private async Task<(byte[], long)> GenerateDirectoryListingAsync(
|
||||
IHttpContext context,
|
||||
MappedResourceInfo info,
|
||||
CompressionMethod compressionMethod)
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var stream = new CompressionStream(memoryStream, compressionMethod);
|
||||
|
||||
await DirectoryLister!.ListDirectoryAsync(
|
||||
info,
|
||||
context.Request.Url.AbsolutePath,
|
||||
Provider.GetDirectoryEntries(info.Path, context),
|
||||
stream,
|
||||
context.CancellationToken).ConfigureAwait(false);
|
||||
|
||||
return (memoryStream.ToArray(), stream.UncompressedLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
282
Vendor/EmbedIO-3.5.2/Files/FileModuleExtensions.cs
vendored
Normal file
282
Vendor/EmbedIO-3.5.2/Files/FileModuleExtensions.cs
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="FileModule"/> and derived classes.
|
||||
/// </summary>
|
||||
public static class FileModuleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the <see cref="FileCache"/> used by a module to store hashes and,
|
||||
/// optionally, file contents and rendered directory listings.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value">An instance of <see cref="FileCache"/>.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.Cache">Cache</see> property
|
||||
/// set to <paramref name="value"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileModule.Cache"/>
|
||||
public static TModule WithCache<TModule>(this TModule @this, FileCache value)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.Cache = value;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value indicating whether a module caches the contents of files
|
||||
/// and directory listings.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value"><see langword="true"/> to enable caching of contents;
|
||||
/// <see langword="false"/> to disable it.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.ContentCaching">ContentCaching</see> property
|
||||
/// set to <paramref name="value"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.ContentCaching"/>
|
||||
public static TModule WithContentCaching<TModule>(this TModule @this, bool value)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.ContentCaching = value;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables caching of file contents and directory listings on a module.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.ContentCaching">ContentCaching</see> property
|
||||
/// set to <see langword="true"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.ContentCaching"/>
|
||||
public static TModule WithContentCaching<TModule>(this TModule @this)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.ContentCaching = true;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables caching of file contents and directory listings on a module.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="maxFileSizeKb"><see langword="true"/> sets the maximum size of a single cached file in kilobytes</param>
|
||||
/// <param name="maxSizeKb"><see langword="true"/> sets the maximum total size of cached data in kilobytes</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.ContentCaching">ContentCaching</see> property
|
||||
/// set to <see langword="true"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.ContentCaching"/>
|
||||
public static TModule WithContentCaching<TModule>(this TModule @this, int maxFileSizeKb, int maxSizeKb)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.ContentCaching = true;
|
||||
@this.Cache.MaxFileSizeKb = maxFileSizeKb;
|
||||
@this.Cache.MaxSizeKb = maxSizeKb;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables caching of file contents and directory listings on a module.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.ContentCaching">ContentCaching</see> property
|
||||
/// set to <see langword="false"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.ContentCaching"/>
|
||||
public static TModule WithoutContentCaching<TModule>(this TModule @this)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.ContentCaching = false;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the name of the default document served, if it exists, instead of a directory listing
|
||||
/// when the path of a requested URL maps to a directory.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value">The name of the default document.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DefaultDocument">DefaultDocument</see> property
|
||||
/// set to <paramref name="value"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.DefaultDocument"/>
|
||||
public static TModule WithDefaultDocument<TModule>(this TModule @this, string value)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DefaultDocument = value;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the name of the default document to <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DefaultDocument">DefaultDocument</see> property
|
||||
/// set to <see langword="null"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.DefaultDocument"/>
|
||||
public static TModule WithoutDefaultDocument<TModule>(this TModule @this)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DefaultDocument = null;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default extension appended to requested URL paths that do not map
|
||||
/// to any file or directory.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value">The default extension.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DefaultExtension">DefaultExtension</see> property
|
||||
/// set to <paramref name="value"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="value"/> is a non-<see langword="null"/>,
|
||||
/// non-empty string that does not start with a period (<c>.</c>).</exception>
|
||||
/// <seealso cref="FileModule.DefaultExtension"/>
|
||||
public static TModule WithDefaultExtension<TModule>(this TModule @this, string value)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DefaultExtension = value;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default extension to <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DefaultExtension">DefaultExtension</see> property
|
||||
/// set to <see langword="null"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.DefaultExtension"/>
|
||||
public static TModule WithoutDefaultExtension<TModule>(this TModule @this)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DefaultExtension = null;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="IDirectoryLister"/> interface used to generate
|
||||
/// directory listing in a module.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value">An <see cref="IDirectoryLister"/> interface, or <see langword="null"/>
|
||||
/// to disable the generation of directory listings.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DirectoryLister">DirectoryLister</see> property
|
||||
/// set to <paramref name="value"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.DirectoryLister"/>
|
||||
public static TModule WithDirectoryLister<TModule>(this TModule @this, IDirectoryLister value)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DirectoryLister = value;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a module's <see cref="FileModule.DirectoryLister">DirectoryLister</see> property
|
||||
/// to <see langword="null"/>, disabling the generation of directory listings.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DirectoryLister">DirectoryLister</see> property
|
||||
/// set to <see langword="null"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.DirectoryLister"/>
|
||||
public static TModule WithoutDirectoryLister<TModule>(this TModule @this)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DirectoryLister = null;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a <see cref="FileRequestHandlerCallback"/> that is called by a module whenever
|
||||
/// the requested URL path could not be mapped to any file or directory.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="callback">The method to call.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.OnMappingFailed">OnMappingFailed</see> property
|
||||
/// set to <paramref name="callback"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileModule.OnMappingFailed"/>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public static TModule HandleMappingFailed<TModule>(this TModule @this, FileRequestHandlerCallback callback)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.OnMappingFailed = callback;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a <see cref="FileRequestHandlerCallback"/> that is called by a module whenever
|
||||
/// the requested URL path has been mapped to a directory, but directory listing has been
|
||||
/// disabled.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="callback">The method to call.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.OnDirectoryNotListable">OnDirectoryNotListable</see> property
|
||||
/// set to <paramref name="callback"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileModule.OnDirectoryNotListable"/>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public static TModule HandleDirectoryNotListable<TModule>(this TModule @this, FileRequestHandlerCallback callback)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.OnDirectoryNotListable = callback;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a <see cref="FileRequestHandlerCallback"/> that is called by a module whenever
|
||||
/// the requested URL path has been mapped to a file or directory, but the request's
|
||||
/// HTTP method is neither <c>GET</c> nor <c>HEAD</c>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="callback">The method to call.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.OnMethodNotAllowed">OnMethodNotAllowed</see> property
|
||||
/// set to <paramref name="callback"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileModule.OnMethodNotAllowed"/>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public static TModule HandleMethodNotAllowed<TModule>(this TModule @this, FileRequestHandlerCallback callback)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.OnMethodNotAllowed = callback;
|
||||
return @this;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Vendor/EmbedIO-3.5.2/Files/FileRequestHandler.cs
vendored
Normal file
53
Vendor/EmbedIO-3.5.2/Files/FileRequestHandler.cs
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides standard handler callbacks for <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="FileRequestHandlerCallback"/>
|
||||
public static class FileRequestHandler
|
||||
{
|
||||
#pragma warning disable CA1801 // Unused parameters - Must respect FileRequestHandlerCallback signature.
|
||||
/// <summary>
|
||||
/// <para>Unconditionally passes a request down the module chain.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext"/> interface representing the context of the request.</param>
|
||||
/// <param name="info">If the requested path has been successfully mapped to a resource (file or directory), the result of the mapping;
|
||||
/// otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>This method never returns; it throws an exception instead.</returns>
|
||||
public static Task PassThrough(IHttpContext context, MappedResourceInfo? info)
|
||||
=> throw RequestHandler.PassThrough();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Unconditionally sends a <c>403 Unauthorized</c> response.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext"/> interface representing the context of the request.</param>
|
||||
/// <param name="info">If the requested path has been successfully mapped to a resource (file or directory), the result of the mapping;
|
||||
/// otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>This method never returns; it throws a <see cref="HttpException"/> instead.</returns>
|
||||
public static Task ThrowUnauthorized(IHttpContext context, MappedResourceInfo? info)
|
||||
=> throw HttpException.Unauthorized();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Unconditionally sends a <c>404 Not Found</c> response.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext"/> interface representing the context of the request.</param>
|
||||
/// <param name="info">If the requested path has been successfully mapped to a resource (file or directory), the result of the mapping;
|
||||
/// otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>This method never returns; it throws a <see cref="HttpException"/> instead.</returns>
|
||||
public static Task ThrowNotFound(IHttpContext context, MappedResourceInfo? info)
|
||||
=> throw HttpException.NotFound();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Unconditionally sends a <c>405 Method Not Allowed</c> response.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext"/> interface representing the context of the request.</param>
|
||||
/// <param name="info">If the requested path has been successfully mapped to a resource (file or directory), the result of the mapping;
|
||||
/// otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>This method never returns; it throws a <see cref="HttpException"/> instead.</returns>
|
||||
public static Task ThrowMethodNotAllowed(IHttpContext context, MappedResourceInfo? info)
|
||||
=> throw HttpException.MethodNotAllowed();
|
||||
#pragma warning restore CA1801
|
||||
}
|
||||
}
|
||||
13
Vendor/EmbedIO-3.5.2/Files/FileRequestHandlerCallback.cs
vendored
Normal file
13
Vendor/EmbedIO-3.5.2/Files/FileRequestHandlerCallback.cs
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// A callback used to handle a request in <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext"/> interface representing the context of the request.</param>
|
||||
/// <param name="info">If the requested path has been successfully mapped to a resource (file or directory), the result of the mapping;
|
||||
/// otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
public delegate Task FileRequestHandlerCallback(IHttpContext context, MappedResourceInfo? info);
|
||||
}
|
||||
202
Vendor/EmbedIO-3.5.2/Files/FileSystemProvider.cs
vendored
Normal file
202
Vendor/EmbedIO-3.5.2/Files/FileSystemProvider.cs
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the local file system to a <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IFileProvider" />
|
||||
public class FileSystemProvider : IDisposable, IFileProvider
|
||||
{
|
||||
private readonly FileSystemWatcher? _watcher;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileSystemProvider"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// OSX doesn't support <see cref="FileSystemWatcher" />, the parameter <paramref name="isImmutable" /> will be always <see langword="true"/>.
|
||||
/// </remarks>
|
||||
/// <param name="fileSystemPath">The file system path.</param>
|
||||
/// <param name="isImmutable"><see langword="true"/> if files and directories in
|
||||
/// <paramref name="fileSystemPath"/> are not expected to change during a web server's
|
||||
/// lifetime; <see langword="false"/> otherwise.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="fileSystemPath"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="fileSystemPath"/> is not a valid local path.</exception>
|
||||
/// <seealso cref="Validate.LocalPath"/>
|
||||
public FileSystemProvider(string fileSystemPath, bool isImmutable)
|
||||
{
|
||||
FileSystemPath = Validate.LocalPath(nameof(fileSystemPath), fileSystemPath, true);
|
||||
IsImmutable = isImmutable || RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
try
|
||||
{
|
||||
if (!IsImmutable)
|
||||
_watcher = new FileSystemWatcher(FileSystemPath);
|
||||
}
|
||||
catch (PlatformNotSupportedException)
|
||||
{
|
||||
IsImmutable = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="FileSystemProvider"/> class.
|
||||
/// </summary>
|
||||
~FileSystemProvider()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<string>? ResourceChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file system path from which files are retrieved.
|
||||
/// </summary>
|
||||
public string FileSystemPath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsImmutable { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Start(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_watcher != null)
|
||||
{
|
||||
_watcher.Changed += Watcher_ChangedOrDeleted;
|
||||
_watcher.Deleted += Watcher_ChangedOrDeleted;
|
||||
_watcher.Renamed += Watcher_Renamed;
|
||||
_watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MappedResourceInfo? MapUrlPath(string urlPath, IMimeTypeProvider mimeTypeProvider)
|
||||
{
|
||||
urlPath = urlPath.Substring(1); // Drop the initial slash
|
||||
string localPath;
|
||||
|
||||
// Disable CA1031 as there's little we can do if IsPathRooted or GetFullPath fails.
|
||||
#pragma warning disable CA1031
|
||||
try
|
||||
{
|
||||
// Unescape the url before continue
|
||||
urlPath = Uri.UnescapeDataString(urlPath);
|
||||
|
||||
// Bail out early if the path is a rooted path,
|
||||
// as Path.Combine would ignore our base path.
|
||||
// See https://docs.microsoft.com/en-us/dotnet/api/system.io.path.combine
|
||||
// (particularly the Remarks section).
|
||||
//
|
||||
// Under Windows, a relative URL path may be a full filesystem path
|
||||
// (e.g. "D:\foo\bar" or "\\192.168.0.1\Shared\MyDocuments\BankAccounts.docx").
|
||||
// Under Unix-like operating systems we have no such problems, as relativeUrlPath
|
||||
// can never start with a slash; however, loading one more class from Swan
|
||||
// just to check the OS type would probably outweigh calling IsPathRooted.
|
||||
if (Path.IsPathRooted(urlPath))
|
||||
return null;
|
||||
|
||||
// Convert the relative URL path to a relative filesystem path
|
||||
// (practically a no-op under Unix-like operating systems)
|
||||
// and combine it with our base local path to obtain a full path.
|
||||
localPath = Path.Combine(FileSystemPath, urlPath.Replace('/', Path.DirectorySeparatorChar));
|
||||
|
||||
// Use GetFullPath as an additional safety check
|
||||
// for relative paths that contain a rooted path
|
||||
// (e.g. "valid/path/C:\Windows\System.ini")
|
||||
localPath = Path.GetFullPath(localPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Both IsPathRooted and GetFullPath throw exceptions
|
||||
// if a path contains invalid characters or is otherwise invalid;
|
||||
// bail out in this case too, as the path would not exist on disk anyway.
|
||||
return null;
|
||||
}
|
||||
#pragma warning restore CA1031
|
||||
|
||||
// As a final precaution, check that the resulting local path
|
||||
// is inside the folder intended to be served.
|
||||
if (!localPath.StartsWith(FileSystemPath, StringComparison.Ordinal))
|
||||
return null;
|
||||
|
||||
if (File.Exists(localPath))
|
||||
return GetMappedFileInfo(mimeTypeProvider, localPath);
|
||||
|
||||
if (Directory.Exists(localPath))
|
||||
return GetMappedDirectoryInfo(localPath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream OpenFile(string path) => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MappedResourceInfo> GetDirectoryEntries(string path, IMimeTypeProvider mimeTypeProvider)
|
||||
=> new DirectoryInfo(path).EnumerateFileSystemInfos()
|
||||
.Select(fsi => GetMappedResourceInfo(mimeTypeProvider, fsi));
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources;
|
||||
/// <see langword="false"/> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
ResourceChanged = null; // Release references to listeners
|
||||
|
||||
if (_watcher != null)
|
||||
{
|
||||
_watcher.EnableRaisingEvents = false;
|
||||
_watcher.Changed -= Watcher_ChangedOrDeleted;
|
||||
_watcher.Deleted -= Watcher_ChangedOrDeleted;
|
||||
_watcher.Renamed -= Watcher_Renamed;
|
||||
|
||||
if (disposing)
|
||||
_watcher.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static MappedResourceInfo GetMappedFileInfo(IMimeTypeProvider mimeTypeProvider, string localPath)
|
||||
=> GetMappedFileInfo(mimeTypeProvider, new FileInfo(localPath));
|
||||
|
||||
private static MappedResourceInfo GetMappedFileInfo(IMimeTypeProvider mimeTypeProvider, FileInfo info)
|
||||
=> MappedResourceInfo.ForFile(
|
||||
info.FullName,
|
||||
info.Name,
|
||||
info.LastWriteTimeUtc,
|
||||
info.Length,
|
||||
mimeTypeProvider.GetMimeType(info.Extension));
|
||||
|
||||
private static MappedResourceInfo GetMappedDirectoryInfo(string localPath)
|
||||
=> GetMappedDirectoryInfo(new DirectoryInfo(localPath));
|
||||
|
||||
private static MappedResourceInfo GetMappedDirectoryInfo(DirectoryInfo info)
|
||||
=> MappedResourceInfo.ForDirectory(info.FullName, info.Name, info.LastWriteTimeUtc);
|
||||
|
||||
private static MappedResourceInfo GetMappedResourceInfo(IMimeTypeProvider mimeTypeProvider, FileSystemInfo info)
|
||||
=> info is DirectoryInfo directoryInfo
|
||||
? GetMappedDirectoryInfo(directoryInfo)
|
||||
: GetMappedFileInfo(mimeTypeProvider, (FileInfo) info);
|
||||
|
||||
private void Watcher_ChangedOrDeleted(object sender, FileSystemEventArgs e)
|
||||
=> ResourceChanged?.Invoke(e.FullPath);
|
||||
|
||||
private void Watcher_Renamed(object sender, RenamedEventArgs e)
|
||||
=> ResourceChanged?.Invoke(e.OldFullPath);
|
||||
}
|
||||
}
|
||||
35
Vendor/EmbedIO-3.5.2/Files/IDirectoryLister.cs
vendored
Normal file
35
Vendor/EmbedIO-3.5.2/Files/IDirectoryLister.cs
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that can render a directory listing to a stream.
|
||||
/// </summary>
|
||||
public interface IDirectoryLister
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the MIME type of generated directory listings.
|
||||
/// </summary>
|
||||
string ContentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously generate a directory listing.
|
||||
/// </summary>
|
||||
/// <param name="info">A <see cref="MappedResourceInfo"/> containing information about
|
||||
/// the directory which is to be listed.</param>
|
||||
/// <param name="absoluteUrlPath">The absolute URL path that was mapped to <paramref name="info"/>.</param>
|
||||
/// <param name="entries">An enumeration of the entries in the directory represented by <paramref name="info"/>.</param>
|
||||
/// <param name="stream">A <see cref="Stream"/> to which the directory listing must be written.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
Task ListDirectoryAsync(
|
||||
MappedResourceInfo info,
|
||||
string absoluteUrlPath,
|
||||
IEnumerable<MappedResourceInfo> entries,
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
61
Vendor/EmbedIO-3.5.2/Files/IFileProvider.cs
vendored
Normal file
61
Vendor/EmbedIO-3.5.2/Files/IFileProvider.cs
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that can provide files and/or directories to be served by a <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
public interface IFileProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Occurs when a file or directory provided by this instance is modified or removed.</para>
|
||||
/// <para>The event's parameter is the provider-specific path of the resource that changed.</para>
|
||||
/// </summary>
|
||||
event Action<string> ResourceChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the files and directories provided by this instance
|
||||
/// will never change.
|
||||
/// </summary>
|
||||
bool IsImmutable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Signals a file provider that the web server is starting.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to stop the web server.</param>
|
||||
void Start(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a URL path to a provider-specific path.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <param name="mimeTypeProvider">An <see cref="IMimeTypeProvider"/> interface to use
|
||||
/// for determining the MIME type of a file.</param>
|
||||
/// <returns>A provider-specific path identifying a file or directory,
|
||||
/// or <see langword="null"/> if this instance cannot provide a resource associated
|
||||
/// to <paramref name="urlPath"/>.</returns>
|
||||
MappedResourceInfo? MapUrlPath(string urlPath, IMimeTypeProvider mimeTypeProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a file for reading.
|
||||
/// </summary>
|
||||
/// <param name="path">The provider-specific path for the file.</param>
|
||||
/// <returns>
|
||||
/// <para>A readable <see cref="Stream"/> of the file's contents.</para>
|
||||
/// </returns>
|
||||
Stream OpenFile(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumeration of the entries of a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The provider-specific path for the directory.</param>
|
||||
/// <param name="mimeTypeProvider">An <see cref="IMimeTypeProvider"/> interface to use
|
||||
/// for determining the MIME type of files.</param>
|
||||
/// <returns>An enumeration of <see cref="MappedResourceInfo"/> objects identifying the entries
|
||||
/// in the directory identified by <paramref name="path"/>.</returns>
|
||||
IEnumerable<MappedResourceInfo> GetDirectoryEntries(string path, IMimeTypeProvider mimeTypeProvider);
|
||||
}
|
||||
}
|
||||
12
Vendor/EmbedIO-3.5.2/Files/Internal/Base64Utility.cs
vendored
Normal file
12
Vendor/EmbedIO-3.5.2/Files/Internal/Base64Utility.cs
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Files.Internal
|
||||
{
|
||||
internal static class Base64Utility
|
||||
{
|
||||
// long is 8 bytes
|
||||
// base64 of 8 bytes is 12 chars, but the last one is padding
|
||||
public static string LongToBase64(long value)
|
||||
=> Convert.ToBase64String(BitConverter.GetBytes(value)).Substring(0, 11);
|
||||
}
|
||||
}
|
||||
28
Vendor/EmbedIO-3.5.2/Files/Internal/EntityTag.cs
vendored
Normal file
28
Vendor/EmbedIO-3.5.2/Files/Internal/EntityTag.cs
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO.Files.Internal
|
||||
{
|
||||
internal static class EntityTag
|
||||
{
|
||||
public static string Compute(DateTime lastModifiedUtc, long length, CompressionMethod compressionMethod)
|
||||
{
|
||||
var sb = new StringBuilder()
|
||||
.Append('"')
|
||||
.Append(Base64Utility.LongToBase64(lastModifiedUtc.Ticks))
|
||||
.Append(Base64Utility.LongToBase64(length));
|
||||
|
||||
switch (compressionMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
sb.Append('-').Append(CompressionMethodNames.Deflate);
|
||||
break;
|
||||
case CompressionMethod.Gzip:
|
||||
sb.Append('-').Append(CompressionMethodNames.Gzip);
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.Append('"').ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
164
Vendor/EmbedIO-3.5.2/Files/Internal/FileCacheItem.cs
vendored
Normal file
164
Vendor/EmbedIO-3.5.2/Files/Internal/FileCacheItem.cs
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using EmbedIO.Internal;
|
||||
|
||||
namespace EmbedIO.Files.Internal
|
||||
{
|
||||
internal sealed class FileCacheItem
|
||||
{
|
||||
#pragma warning disable SA1401 // Field should be private - performance is a stronger concern here.
|
||||
// These fields create a sort of linked list of items
|
||||
// inside the cache's dictionary.
|
||||
// Their purpose is to keep track of items
|
||||
// in order from least to most recently used.
|
||||
internal string? PreviousKey;
|
||||
internal string? NextKey;
|
||||
internal long LastUsedAt;
|
||||
#pragma warning restore SA1401
|
||||
|
||||
// Size of a pointer in bytes
|
||||
private static readonly long SizeOfPointer = Environment.Is64BitProcess ? 8 : 4;
|
||||
|
||||
// Size of a WeakReference<T> in bytes
|
||||
private static readonly long SizeOfWeakReference = Environment.Is64BitProcess ? 16 : 32;
|
||||
|
||||
// Educated guess about the size of an Item in memory (see comments on constructor).
|
||||
// 3 * SizeOfPointer + total size of fields, rounded up to a multiple of 16.
|
||||
//
|
||||
// Computed as follows:
|
||||
//
|
||||
// * for 32-bit:
|
||||
// - initialize count to 3 (number of "hidden" pointers that compose the object header)
|
||||
// - for every field / auto property, in order of declaration:
|
||||
// - increment count by 1 for reference types, 2 for long and DateTime
|
||||
// (as of time of writing there are no fields of other types here)
|
||||
// - increment again by 1 if this field "weighs" 1 and the next one "weighs" 2
|
||||
// (padding for field alignment)
|
||||
// - multiply count by 4 (size of a pointer)
|
||||
// - if the result is not a multiple of 16, round it up to next multiple of 16
|
||||
//
|
||||
// * for 64-bit:
|
||||
// - initialize count to 3 (number of "hidden" pointers that compose the object header)
|
||||
// - for every field / auto property, in order of declaration, increment count by 1
|
||||
// (at the time of writing there are no fields here that need padding on 64-bit)
|
||||
// - multiply count by 8 (size of a pointer)
|
||||
// - if the result is not a multiple of 16, round it up to next multiple of 16
|
||||
private static readonly long SizeOfItem = Environment.Is64BitProcess ? 96 : 128;
|
||||
|
||||
private readonly object _syncRoot = new object();
|
||||
|
||||
// Used to update total size of section.
|
||||
// Weak reference avoids circularity.
|
||||
private readonly WeakReference<FileCache.Section> _section;
|
||||
|
||||
// There are only 3 possible compression methods,
|
||||
// hence a dictionary (or two dictionaries) would be overkill.
|
||||
private byte[]? _uncompressedContent;
|
||||
private byte[]? _gzippedContent;
|
||||
private byte[]? _deflatedContent;
|
||||
|
||||
internal FileCacheItem(FileCache.Section section, DateTime lastModifiedUtc, long length)
|
||||
{
|
||||
_section = new WeakReference<FileCache.Section>(section);
|
||||
|
||||
LastModifiedUtc = lastModifiedUtc;
|
||||
Length = length;
|
||||
|
||||
// There is no way to know the actual size of an object at runtime.
|
||||
// This method makes some educated guesses, based on the following
|
||||
// article (among others):
|
||||
// https://codingsight.com/precise-computation-of-clr-object-size/
|
||||
// PreviousKey and NextKey values aren't counted in
|
||||
// because they are just references to existing strings.
|
||||
SizeInCache = SizeOfItem + SizeOfWeakReference;
|
||||
}
|
||||
|
||||
public DateTime LastModifiedUtc { get; }
|
||||
|
||||
public long Length { get; }
|
||||
|
||||
// This is the (approximate) in-memory size of this object.
|
||||
// It is NOT the length of the cache resource!
|
||||
public long SizeInCache { get; private set; }
|
||||
|
||||
public byte[]? GetContent(CompressionMethod compressionMethod)
|
||||
{
|
||||
// If there are both entity tag and content, use them.
|
||||
switch (compressionMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
if (_deflatedContent != null) return _deflatedContent;
|
||||
break;
|
||||
case CompressionMethod.Gzip:
|
||||
if (_gzippedContent != null) return _gzippedContent;
|
||||
break;
|
||||
default:
|
||||
if (_uncompressedContent != null) return _uncompressedContent;
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to convert existing content, if any.
|
||||
byte[]? content;
|
||||
if (_uncompressedContent != null)
|
||||
{
|
||||
content = CompressionUtility.ConvertCompression(_uncompressedContent, CompressionMethod.None, compressionMethod);
|
||||
}
|
||||
else if (_gzippedContent != null)
|
||||
{
|
||||
content = CompressionUtility.ConvertCompression(_gzippedContent, CompressionMethod.Gzip, compressionMethod);
|
||||
}
|
||||
else if (_deflatedContent != null)
|
||||
{
|
||||
content = CompressionUtility.ConvertCompression(_deflatedContent, CompressionMethod.Deflate, compressionMethod);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No content whatsoever.
|
||||
return null;
|
||||
}
|
||||
|
||||
return SetContent(compressionMethod, content);
|
||||
}
|
||||
|
||||
public byte[]? SetContent(CompressionMethod compressionMethod, byte[]? content)
|
||||
{
|
||||
// This is the bare minimum locking we need
|
||||
// to ensure we don't mess sizes up.
|
||||
byte[]? oldContent;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
switch (compressionMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
oldContent = _deflatedContent;
|
||||
_deflatedContent = content;
|
||||
break;
|
||||
case CompressionMethod.Gzip:
|
||||
oldContent = _gzippedContent;
|
||||
_gzippedContent = content;
|
||||
break;
|
||||
default:
|
||||
oldContent = _uncompressedContent;
|
||||
_uncompressedContent = content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var sizeDelta = GetSizeOf(content) - GetSizeOf(oldContent);
|
||||
SizeInCache += sizeDelta;
|
||||
if (_section.TryGetTarget(out var section))
|
||||
section.UpdateTotalSize(sizeDelta);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// Round up to a multiple of 16
|
||||
private static long RoundUpTo16(long n)
|
||||
{
|
||||
var remainder = n % 16;
|
||||
return remainder > 0 ? n + (16 - remainder) : n;
|
||||
}
|
||||
|
||||
// The size of a byte array is 3 * SizeOfPointer + 1 (size of byte) * Length
|
||||
private static long GetSizeOf(byte[]? arr) => arr == null ? 0 : RoundUpTo16(3 * SizeOfPointer) + arr.Length;
|
||||
}
|
||||
}
|
||||
73
Vendor/EmbedIO-3.5.2/Files/Internal/HtmlDirectoryLister.cs
vendored
Normal file
73
Vendor/EmbedIO-3.5.2/Files/Internal/HtmlDirectoryLister.cs
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan;
|
||||
|
||||
namespace EmbedIO.Files.Internal
|
||||
{
|
||||
internal class HtmlDirectoryLister : IDirectoryLister
|
||||
{
|
||||
private static readonly Lazy<IDirectoryLister> LazyInstance = new Lazy<IDirectoryLister>(() => new HtmlDirectoryLister());
|
||||
|
||||
private HtmlDirectoryLister()
|
||||
{
|
||||
}
|
||||
|
||||
public static IDirectoryLister Instance => LazyInstance.Value;
|
||||
|
||||
public string ContentType { get; } = MimeType.Html + "; encoding=" + WebServer.DefaultEncoding.WebName;
|
||||
|
||||
public async Task ListDirectoryAsync(
|
||||
MappedResourceInfo info,
|
||||
string absoluteUrlPath,
|
||||
IEnumerable<MappedResourceInfo> entries,
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
const int MaxEntryLength = 50;
|
||||
const int SizeIndent = -20; // Negative for right alignment
|
||||
|
||||
if (!info.IsDirectory)
|
||||
throw SelfCheck.Failure($"{nameof(HtmlDirectoryLister)}.{nameof(ListDirectoryAsync)} invoked with a file, not a directory.");
|
||||
|
||||
var encodedPath = WebUtility.HtmlEncode(absoluteUrlPath);
|
||||
using var text = new StreamWriter(stream, WebServer.DefaultEncoding);
|
||||
text.Write("<html><head><title>Index of ");
|
||||
text.Write(encodedPath);
|
||||
text.Write("</title></head><body><h1>Index of ");
|
||||
text.Write(encodedPath);
|
||||
text.Write("</h1><hr/><pre>");
|
||||
|
||||
if (encodedPath.Length > 1)
|
||||
text.Write("<a href='../'>../</a>\n");
|
||||
|
||||
entries = entries.ToArray();
|
||||
|
||||
foreach (var directory in entries.Where(m => m.IsDirectory).OrderBy(e => e.Name))
|
||||
{
|
||||
text.Write($"<a href=\"{Uri.EscapeDataString(directory.Name)}\">{WebUtility.HtmlEncode(directory.Name)}</a>");
|
||||
text.Write(new string(' ', Math.Max(1, MaxEntryLength - directory.Name.Length + 1)));
|
||||
text.Write(HttpDate.Format(directory.LastModifiedUtc));
|
||||
text.Write('\n');
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
foreach (var file in entries.Where(m => m.IsFile).OrderBy(e => e.Name))
|
||||
{
|
||||
text.Write($"<a href=\"{Uri.EscapeDataString(file.Name)}\">{WebUtility.HtmlEncode(file.Name)}</a>");
|
||||
text.Write(new string(' ', Math.Max(1, MaxEntryLength - file.Name.Length + 1)));
|
||||
text.Write(HttpDate.Format(file.LastModifiedUtc));
|
||||
text.Write($" {file.Length.ToString("#,###", CultureInfo.InvariantCulture),SizeIndent}\n");
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
text.Write("</pre><hr/></body></html>");
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Vendor/EmbedIO-3.5.2/Files/Internal/MappedResourceInfoExtensions.cs
vendored
Normal file
8
Vendor/EmbedIO-3.5.2/Files/Internal/MappedResourceInfoExtensions.cs
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace EmbedIO.Files.Internal
|
||||
{
|
||||
internal static class MappedResourceInfoExtensions
|
||||
{
|
||||
public static string GetEntityTag(this MappedResourceInfo @this, CompressionMethod compressionMethod)
|
||||
=> EntityTag.Compute(@this.LastModifiedUtc, @this.Length, compressionMethod);
|
||||
}
|
||||
}
|
||||
80
Vendor/EmbedIO-3.5.2/Files/MappedResourceInfo.cs
vendored
Normal file
80
Vendor/EmbedIO-3.5.2/Files/MappedResourceInfo.cs
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about a resource served via an <see cref="IFileProvider"/>.
|
||||
/// </summary>
|
||||
public sealed class MappedResourceInfo
|
||||
{
|
||||
private MappedResourceInfo(string path, string name, DateTime lastModifiedUtc, long length, string? contentType)
|
||||
{
|
||||
Path = path;
|
||||
Name = name;
|
||||
LastModifiedUtc = lastModifiedUtc;
|
||||
Length = length;
|
||||
ContentType = contentType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance represents a directory.
|
||||
/// </summary>
|
||||
public bool IsDirectory => ContentType == null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance represents a file.
|
||||
/// </summary>
|
||||
public bool IsFile => ContentType != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique, provider-specific path for the resource.
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the resource, as it would appear in a directory listing.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UTC date and time of the last modification made to the resource.
|
||||
/// </summary>
|
||||
public DateTime LastModifiedUtc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>If <see cref="IsDirectory"/> is <see langword="false"/>, gets the length of the file, expressed in bytes.</para>
|
||||
/// <para>If <see cref="IsDirectory"/> is <see langword="true"/>, this property is always zero.</para>
|
||||
/// </summary>
|
||||
public long Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>If <see cref="IsDirectory"/> is <see langword="false"/>, gets a MIME type describing the kind of contents of the file.</para>
|
||||
/// <para>If <see cref="IsDirectory"/> is <see langword="true"/>, this property is always <see langword="null"/>.</para>
|
||||
/// </summary>
|
||||
public string? ContentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a new instance of the <see cref="MappedResourceInfo"/> class,
|
||||
/// representing a file.
|
||||
/// </summary>
|
||||
/// <param name="path">A unique, provider-specific path for the file.</param>
|
||||
/// <param name="name">The name of the file, as it would appear in a directory listing.</param>
|
||||
/// <param name="lastModifiedUtc">The UTC date and time of the last modification made to the file.</param>
|
||||
/// <param name="size">The length of the file, expressed in bytes.</param>
|
||||
/// <param name="contentType">A MIME type describing the kind of contents of the file.</param>
|
||||
/// <returns>A newly-constructed instance of <see cref="MappedResourceInfo"/>.</returns>
|
||||
public static MappedResourceInfo ForFile(string path, string name, DateTime lastModifiedUtc, long size, string contentType)
|
||||
=> new MappedResourceInfo(path, name, lastModifiedUtc, size, contentType ?? MimeType.Default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a new instance of the <see cref="MappedResourceInfo"/> class,
|
||||
/// representing a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">A unique, provider-specific path for the directory.</param>
|
||||
/// <param name="name">The name of the directory, as it would appear in a directory listing.</param>
|
||||
/// <param name="lastModifiedUtc">The UTC date and time of the last modification made to the directory.</param>
|
||||
/// <returns>A newly-constructed instance of <see cref="MappedResourceInfo"/>.</returns>
|
||||
public static MappedResourceInfo ForDirectory(string path, string name, DateTime lastModifiedUtc)
|
||||
=> new MappedResourceInfo(path, name, lastModifiedUtc, 0, null);
|
||||
}
|
||||
}
|
||||
94
Vendor/EmbedIO-3.5.2/Files/ResourceFileProvider.cs
vendored
Normal file
94
Vendor/EmbedIO-3.5.2/Files/ResourceFileProvider.cs
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to embedded resources to a <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IFileProvider" />
|
||||
public class ResourceFileProvider : IFileProvider
|
||||
{
|
||||
private readonly DateTime _fileTime = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResourceFileProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly where served files are contained as embedded resources.</param>
|
||||
/// <param name="pathPrefix">A string to prepend to provider-specific paths
|
||||
/// to form the name of a manifest resource in <paramref name="assembly"/>.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assembly"/> is <see langword="null"/>.</exception>
|
||||
public ResourceFileProvider(Assembly assembly, string pathPrefix)
|
||||
{
|
||||
Assembly = Validate.NotNull(nameof(assembly), assembly);
|
||||
PathPrefix = pathPrefix ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<string> ResourceChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly where served files are contained as embedded resources.
|
||||
/// </summary>
|
||||
public Assembly Assembly { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string that is prepended to provider-specific paths to form the name of a manifest resource in <see cref="Assembly"/>.
|
||||
/// </summary>
|
||||
public string PathPrefix { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsImmutable => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Start(CancellationToken cancellationToken)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MappedResourceInfo? MapUrlPath(string urlPath, IMimeTypeProvider mimeTypeProvider)
|
||||
{
|
||||
var resourceName = PathPrefix + urlPath.Replace('/', '.');
|
||||
|
||||
long size;
|
||||
try
|
||||
{
|
||||
using var stream = Assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream == null || stream == Stream.Null)
|
||||
return null;
|
||||
|
||||
size = stream.Length;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var lastSlashPos = urlPath.LastIndexOf('/');
|
||||
var name = urlPath.Substring(lastSlashPos + 1);
|
||||
|
||||
return MappedResourceInfo.ForFile(
|
||||
resourceName,
|
||||
name,
|
||||
_fileTime,
|
||||
size,
|
||||
mimeTypeProvider.GetMimeType(Path.GetExtension(name)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream OpenFile(string path) => Assembly.GetManifestResourceStream(path);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MappedResourceInfo> GetDirectoryEntries(string path, IMimeTypeProvider mimeTypeProvider)
|
||||
=> Enumerable.Empty<MappedResourceInfo>();
|
||||
}
|
||||
}
|
||||
110
Vendor/EmbedIO-3.5.2/Files/ZipFileProvider.cs
vendored
Normal file
110
Vendor/EmbedIO-3.5.2/Files/ZipFileProvider.cs
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
using EmbedIO.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to files contained in a <c>.zip</c> file to a <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IFileProvider" />
|
||||
public class ZipFileProvider : IDisposable, IFileProvider
|
||||
{
|
||||
private readonly ZipArchive _zipArchive;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ZipFileProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="zipFilePath">The zip file path.</param>
|
||||
public ZipFileProvider(string zipFilePath)
|
||||
: this(new FileStream(Validate.LocalPath(nameof(zipFilePath), zipFilePath, true), FileMode.Open))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ZipFileProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream that contains the archive.</param>
|
||||
/// <param name="leaveOpen"><see langword="true"/> to leave the stream open after the web server
|
||||
/// is disposed; otherwise, <see langword="false"/>.</param>
|
||||
public ZipFileProvider(Stream stream, bool leaveOpen = false)
|
||||
{
|
||||
_zipArchive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ZipFileProvider"/> class.
|
||||
/// </summary>
|
||||
~ZipFileProvider()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<string> ResourceChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsImmutable => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Start(CancellationToken cancellationToken)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MappedResourceInfo? MapUrlPath(string urlPath, IMimeTypeProvider mimeTypeProvider)
|
||||
{
|
||||
if (urlPath.Length == 1)
|
||||
return null;
|
||||
|
||||
urlPath = Uri.UnescapeDataString(urlPath);
|
||||
|
||||
var entry = _zipArchive.GetEntry(urlPath.Substring(1));
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
return MappedResourceInfo.ForFile(
|
||||
entry.FullName,
|
||||
entry.Name,
|
||||
entry.LastWriteTime.DateTime,
|
||||
entry.Length,
|
||||
mimeTypeProvider.GetMimeType(Path.GetExtension(entry.Name)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream OpenFile(string path)
|
||||
=> _zipArchive.GetEntry(path)?.Open() ?? throw new FileNotFoundException($"\"{path}\" cannot be found in Zip archive.");
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MappedResourceInfo> GetDirectoryEntries(string path, IMimeTypeProvider mimeTypeProvider)
|
||||
=> Enumerable.Empty<MappedResourceInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources;
|
||||
/// <see langword="false"/> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_zipArchive.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Items.cs
vendored
Normal file
47
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Items.cs
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
/// <summary>Gets the item associated with the specified key.</summary>
|
||||
/// <typeparam name="T">The desired type of the item.</typeparam>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <param name="key">The key whose value to get from the <see cref="IHttpContext.Items">Items</see> dictionary.</param>
|
||||
/// <param name="value">
|
||||
/// <para>When this method returns, the item associated with the specified key,
|
||||
/// if the key is found in <see cref="IHttpContext.Items">Items</see>
|
||||
/// and the associated value is of type <typeparamref name="T"/>;
|
||||
/// otherwise, the default value for <typeparamref name="T"/>.</para>
|
||||
/// <para>This parameter is passed uninitialized.</para>
|
||||
/// </param>
|
||||
/// <returns><see langword="true"/> if the item is found and is of type <typeparamref name="T"/>;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
|
||||
public static bool TryGetItem<T>(this IHttpContext @this, object key, out T value)
|
||||
{
|
||||
if (@this.Items.TryGetValue(key, out var item) && item is T typedItem)
|
||||
{
|
||||
value = typedItem;
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma warning disable CS8653 // value is non-nullable - We are returning false, so value is undefined.
|
||||
value = default;
|
||||
#pragma warning restore CS8653
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Gets the item associated with the specified key.</summary>
|
||||
/// <typeparam name="T">The desired type of the item.</typeparam>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <param name="key">The key whose value to get from the <see cref="IHttpContext.Items">Items</see> dictionary.</param>
|
||||
/// <returns>The item associated with the specified key,
|
||||
/// if the key is found in <see cref="IHttpContext.Items">Items</see>
|
||||
/// and the associated value is of type <typeparamref name="T"/>;
|
||||
/// otherwise, the default value for <typeparamref name="T"/>.</returns>
|
||||
public static T GetItem<T>(this IHttpContext @this, object key)
|
||||
=> @this.Items.TryGetValue(key, out var item) && item is T typedItem ? typedItem : default;
|
||||
}
|
||||
}
|
||||
33
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Redirect.cs
vendored
Normal file
33
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Redirect.cs
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets a redirection status code and adds a <c>Location</c> header to the response.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <param name="location">The URL to which the user agent should be redirected.</param>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="location"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="location"/> is not a valid relative or absolute URL.<see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="statusCode"/> is not a redirection (3xx) status code.</para>
|
||||
/// </exception>
|
||||
public static void Redirect(this IHttpContext @this, string location, int statusCode = (int)HttpStatusCode.Found)
|
||||
{
|
||||
location = Validate.Url(nameof(location), location, @this.Request.Url);
|
||||
|
||||
if (statusCode < 300 || statusCode > 399)
|
||||
throw new ArgumentException("Redirect status code is not valid.", nameof(statusCode));
|
||||
|
||||
@this.Response.SetEmptyResponse(statusCode);
|
||||
@this.Response.Headers[HttpHeaderNames.Location] = location;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Vendor/EmbedIO-3.5.2/HttpContextExtensions-RequestStream.cs
vendored
Normal file
61
Vendor/EmbedIO-3.5.2/HttpContextExtensions-RequestStream.cs
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Wraps the request input stream and returns a <see cref="Stream"/> that can be used directly.</para>
|
||||
/// <para>Decompression of compressed request bodies is implemented if specified in the web server's options.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>
|
||||
/// <para>A <see cref="Stream"/> that can be used to write response data.</para>
|
||||
/// <para>This stream MUST be disposed when finished writing.</para>
|
||||
/// </returns>
|
||||
/// <seealso cref="OpenRequestText"/>
|
||||
/// <seealso cref="WebServerOptionsBase.SupportCompressedRequests"/>
|
||||
public static Stream OpenRequestStream(this IHttpContext @this)
|
||||
{
|
||||
var stream = @this.Request.InputStream;
|
||||
|
||||
var encoding = @this.Request.Headers[HttpHeaderNames.ContentEncoding]?.Trim();
|
||||
switch (encoding)
|
||||
{
|
||||
case CompressionMethodNames.Gzip:
|
||||
if (@this.SupportCompressedRequests)
|
||||
return new GZipStream(stream, CompressionMode.Decompress);
|
||||
break;
|
||||
case CompressionMethodNames.Deflate:
|
||||
if (@this.SupportCompressedRequests)
|
||||
return new DeflateStream(stream, CompressionMode.Decompress);
|
||||
break;
|
||||
case CompressionMethodNames.None:
|
||||
case null:
|
||||
return stream;
|
||||
}
|
||||
|
||||
$"[{@this.Id}] Unsupported request content encoding \"{encoding}\", sending 400 Bad Request..."
|
||||
.Warn(nameof(OpenRequestStream));
|
||||
|
||||
throw HttpException.BadRequest($"Unsupported content encoding \"{encoding}\"");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Wraps the request input stream and returns a <see cref="TextReader" /> that can be used directly.</para>
|
||||
/// <para>Decompression of compressed request bodies is implemented if specified in the web server's options.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext" /> on which this method is called.</param>
|
||||
/// <returns>
|
||||
/// <para>A <see cref="TextReader" /> that can be used to read the request body as text.</para>
|
||||
/// <para>This reader MUST be disposed when finished reading.</para>
|
||||
/// </returns>
|
||||
/// <seealso cref="OpenRequestStream"/>
|
||||
/// <seealso cref="WebServerOptionsBase.SupportCompressedRequests"/>
|
||||
public static TextReader OpenRequestText(this IHttpContext @this)
|
||||
=> new StreamReader(OpenRequestStream(@this), @this.Request.ContentEncoding);
|
||||
}
|
||||
}
|
||||
175
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Requests.cs
vendored
Normal file
175
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Requests.cs
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
private static readonly object FormDataKey = new object();
|
||||
private static readonly object QueryDataKey = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the request body as an array of <see langword="byte"/>s.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be an array of <see cref="byte"/>s containing the request body.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static async Task<byte[]> GetRequestBodyAsByteArrayAsync(this IHttpContext @this)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var stream = @this.OpenRequestStream();
|
||||
await stream.CopyToAsync(buffer, WebServer.StreamCopyBufferSize, @this.CancellationToken).ConfigureAwait(false);
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously buffers the request body into a read-only <see cref="MemoryStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be a read-only <see cref="MemoryStream"/> containing the request body.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static async Task<MemoryStream> GetRequestBodyAsMemoryStreamAsync(this IHttpContext @this)
|
||||
=> new MemoryStream(
|
||||
await GetRequestBodyAsByteArrayAsync(@this).ConfigureAwait(false),
|
||||
false);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the request body as a string.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be a <see langword="string"/> representation of the request body.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static async Task<string> GetRequestBodyAsStringAsync(this IHttpContext @this)
|
||||
{
|
||||
using var reader = @this.OpenRequestText();
|
||||
return await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Asynchronously deserializes a request body, using the default request deserializer.</para>
|
||||
/// <para>As of EmbedIO version 3.0, the default response serializer has the same behavior of JSON
|
||||
/// request parsing methods of version 2.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">The expected type of the deserialized data.</typeparam>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be the deserialized data.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static Task<TData> GetRequestDataAsync<TData>(this IHttpContext @this)
|
||||
=> RequestDeserializer.Default<TData>(@this);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deserializes a request body, using the specified request deserializer.
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">The expected type of the deserialized data.</typeparam>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <param name="deserializer">A <see cref="RequestDeserializerCallback{TData}"/> used to deserialize the request body.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be the deserialized data.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="deserializer"/> is <see langword="null"/>.</exception>
|
||||
public static Task<TData> GetRequestDataAsync<TData>(this IHttpContext @this,RequestDeserializerCallback<TData> deserializer)
|
||||
=> Validate.NotNull(nameof(deserializer), deserializer)(@this);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously parses a request body in <c>application/x-www-form-urlencoded</c> format.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be a read-only <see cref="NameValueCollection"/>of form field names and values.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// <para>This method may safely be called more than once for the same <see cref="IHttpContext"/>:
|
||||
/// it will return the same collection instead of trying to parse the request body again.</para>
|
||||
/// </remarks>
|
||||
public static async Task<NameValueCollection> GetRequestFormDataAsync(this IHttpContext @this)
|
||||
{
|
||||
if (!@this.Items.TryGetValue(FormDataKey, out var previousResult))
|
||||
{
|
||||
NameValueCollection result;
|
||||
try
|
||||
{
|
||||
using var reader = @this.OpenRequestText();
|
||||
result = UrlEncodedDataParser.Parse(await reader.ReadToEndAsync().ConfigureAwait(false), false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@this.Items[FormDataKey] = e;
|
||||
throw;
|
||||
}
|
||||
|
||||
@this.Items[FormDataKey] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
switch (previousResult)
|
||||
{
|
||||
case NameValueCollection collection:
|
||||
return collection;
|
||||
|
||||
case Exception exception:
|
||||
throw exception.RethrowPreservingStackTrace();
|
||||
|
||||
case null:
|
||||
throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestFormDataAsync)} is null.");
|
||||
|
||||
default:
|
||||
throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestFormDataAsync)} is of unexpected type {previousResult.GetType().FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a request URL query. Note that this is different from getting the <see cref="IHttpRequest.QueryString"/> property,
|
||||
/// in that fields without an equal sign are treated as if they have an empty value, instead of their keys being grouped
|
||||
/// as values of the <c>null</c> key.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A read-only <see cref="NameValueCollection"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// <para>This method may safely be called more than once for the same <see cref="IHttpContext"/>:
|
||||
/// it will return the same collection instead of trying to parse the request body again.</para>
|
||||
/// </remarks>
|
||||
public static NameValueCollection GetRequestQueryData(this IHttpContext @this)
|
||||
{
|
||||
if (!@this.Items.TryGetValue(QueryDataKey, out var previousResult))
|
||||
{
|
||||
NameValueCollection result;
|
||||
try
|
||||
{
|
||||
result = UrlEncodedDataParser.Parse(@this.Request.Url.Query, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@this.Items[FormDataKey] = e;
|
||||
throw;
|
||||
}
|
||||
|
||||
@this.Items[FormDataKey] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
switch (previousResult)
|
||||
{
|
||||
case NameValueCollection collection:
|
||||
return collection;
|
||||
|
||||
case Exception exception:
|
||||
throw exception.RethrowPreservingStackTrace();
|
||||
|
||||
case null:
|
||||
throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestQueryData)} is null.");
|
||||
|
||||
default:
|
||||
throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestQueryData)} is of unexpected type {previousResult.GetType().FullName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Vendor/EmbedIO-3.5.2/HttpContextExtensions-ResponseStream.cs
vendored
Normal file
68
Vendor/EmbedIO-3.5.2/HttpContextExtensions-ResponseStream.cs
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using EmbedIO.Internal;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Wraps the response output stream and returns a <see cref="Stream"/> that can be used directly.</para>
|
||||
/// <para>Optional buffering is applied, so that the response may be sent as one instead of using chunked transfer.</para>
|
||||
/// <para>Proactive negotiation is performed to select the best compression method supported by the client.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <param name="buffered">If set to <see langword="true"/>, sent data is collected
|
||||
/// in a <see cref="MemoryStream"/> and sent all at once when the returned <see cref="Stream"/>
|
||||
/// is disposed; if set to <see langword="false"/> (the default), chunked transfer will be used.</param>
|
||||
/// <param name="preferCompression"><see langword="true"/> if sending compressed data is preferred over
|
||||
/// sending non-compressed data; otherwise, <see langword="false"/>.</param>
|
||||
/// <returns>
|
||||
/// <para>A <see cref="Stream"/> that can be used to write response data.</para>
|
||||
/// <para>This stream MUST be disposed when finished writing.</para>
|
||||
/// </returns>
|
||||
/// <seealso cref="OpenResponseText"/>
|
||||
public static Stream OpenResponseStream(this IHttpContext @this, bool buffered = false, bool preferCompression = true)
|
||||
{
|
||||
// No need to check whether negotiation is successful;
|
||||
// the returned callback will throw HttpNotAcceptableException if it was not.
|
||||
_ = @this.Request.TryNegotiateContentEncoding(preferCompression, out var compressionMethod, out var prepareResponse);
|
||||
prepareResponse(@this.Response);
|
||||
var stream = buffered ? new BufferingResponseStream(@this.Response) : @this.Response.OutputStream;
|
||||
|
||||
return compressionMethod switch {
|
||||
CompressionMethod.Gzip => new GZipStream(stream, CompressionMode.Compress),
|
||||
CompressionMethod.Deflate => new DeflateStream(stream, CompressionMode.Compress),
|
||||
_ => stream
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Wraps the response output stream and returns a <see cref="TextWriter" /> that can be used directly.</para>
|
||||
/// <para>Optional buffering is applied, so that the response may be sent as one instead of using chunked transfer.</para>
|
||||
/// <para>Proactive negotiation is performed to select the best compression method supported by the client.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext" /> on which this method is called.</param>
|
||||
/// <param name="encoding">
|
||||
/// <para>The <see cref="Encoding"/> to use to convert text to data bytes.</para>
|
||||
/// <para>If <see langword="null"/> (the default), <see cref="WebServer.DefaultEncoding"/> (UTF-8 without a byte order mark) is used.</para>
|
||||
/// </param>
|
||||
/// <param name="buffered">If set to <see langword="true" />, sent data is collected
|
||||
/// in a <see cref="MemoryStream" /> and sent all at once when the returned <see cref="Stream" />
|
||||
/// is disposed; if set to <see langword="false" /> (the default), chunked transfer will be used.</param>
|
||||
/// <param name="preferCompression"><see langword="true"/> if sending compressed data is preferred over
|
||||
/// sending non-compressed data; otherwise, <see langword="false"/>.</param>
|
||||
/// <returns>
|
||||
/// <para>A <see cref="TextWriter" /> that can be used to write response data.</para>
|
||||
/// <para>This writer MUST be disposed when finished writing.</para>
|
||||
/// </returns>
|
||||
/// <seealso cref="OpenResponseStream"/>
|
||||
public static TextWriter OpenResponseText(this IHttpContext @this, Encoding? encoding = null, bool buffered = false, bool preferCompression = true)
|
||||
{
|
||||
encoding ??= WebServer.DefaultEncoding;
|
||||
@this.Response.ContentEncoding = encoding;
|
||||
return new StreamWriter(OpenResponseStream(@this, buffered, preferCompression), encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
124
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Responses.cs
vendored
Normal file
124
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Responses.cs
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
private const string StandardHtmlHeaderFormat = "<html><head><meta charset=\"{2}\"><title>{0} - {1}</title></head><body><h1>{0} - {1}</h1>";
|
||||
private const string StandardHtmlFooter = "</body></html>";
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously sends a string as response.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpResponse"/> interface on which this method is called.</param>
|
||||
/// <param name="content">The response content.</param>
|
||||
/// <param name="contentType">The MIME type of the content. If <see langword="null"/>, the content type will not be set.</param>
|
||||
/// <param name="encoding">The <see cref="Encoding"/> to use.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="content"/> is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="encoding"/> is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
public static async Task SendStringAsync(
|
||||
this IHttpContext @this,
|
||||
string content,
|
||||
string contentType,
|
||||
Encoding encoding)
|
||||
{
|
||||
content = Validate.NotNull(nameof(content), content);
|
||||
encoding = Validate.NotNull(nameof(encoding), encoding);
|
||||
|
||||
if (contentType != null)
|
||||
{
|
||||
@this.Response.ContentType = contentType;
|
||||
@this.Response.ContentEncoding = encoding;
|
||||
}
|
||||
|
||||
using var text = @this.OpenResponseText(encoding);
|
||||
await text.WriteAsync(content).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously sends a standard HTML response for the specified status code.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <param name="statusCode">The HTTP status code of the response.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">There is no standard status description for <paramref name="statusCode"/>.</exception>
|
||||
/// <seealso cref="SendStandardHtmlAsync(IHttpContext,int,Action{TextWriter})"/>
|
||||
public static Task SendStandardHtmlAsync(this IHttpContext @this, int statusCode)
|
||||
=> SendStandardHtmlAsync(@this, statusCode, null);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously sends a standard HTML response for the specified status code.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <param name="statusCode">The HTTP status code of the response.</param>
|
||||
/// <param name="writeAdditionalHtml">A callback function that may write additional HTML code
|
||||
/// to a <see cref="TextWriter"/> representing the response output.
|
||||
/// If not <see langword="null"/>, the callback is called immediately before closing the HTML <c>body</c> tag.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">There is no standard status description for <paramref name="statusCode"/>.</exception>
|
||||
/// <seealso cref="SendStandardHtmlAsync(IHttpContext,int)"/>
|
||||
public static Task SendStandardHtmlAsync(
|
||||
this IHttpContext @this,
|
||||
int statusCode,
|
||||
Action<TextWriter>? writeAdditionalHtml)
|
||||
{
|
||||
if (!HttpStatusDescription.TryGet(statusCode, out var statusDescription))
|
||||
throw new ArgumentException("Status code has no standard description.", nameof(statusCode));
|
||||
|
||||
@this.Response.StatusCode = statusCode;
|
||||
@this.Response.StatusDescription = statusDescription;
|
||||
@this.Response.ContentType = MimeType.Html;
|
||||
@this.Response.ContentEncoding = WebServer.DefaultEncoding;
|
||||
using (var text = @this.OpenResponseText(WebServer.DefaultEncoding))
|
||||
{
|
||||
text.Write(StandardHtmlHeaderFormat, statusCode, statusDescription, WebServer.DefaultEncoding.WebName);
|
||||
writeAdditionalHtml?.Invoke(text);
|
||||
text.Write(StandardHtmlFooter);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Asynchronously sends serialized data as a response, using the default response serializer.</para>
|
||||
/// <para>As of EmbedIO version 3.0, the default response serializer has the same behavior of JSON
|
||||
/// response methods of version 2.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <param name="data">The data to serialize.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="SendDataAsync(IHttpContext,ResponseSerializerCallback,object)"/>
|
||||
/// <seealso cref="ResponseSerializer.Default"/>
|
||||
public static Task SendDataAsync(this IHttpContext @this, object data)
|
||||
=> ResponseSerializer.Default(@this, data);
|
||||
|
||||
/// <summary>
|
||||
/// <para>Asynchronously sends serialized data as a response, using the specified response serializer.</para>
|
||||
/// <para>As of EmbedIO version 3.0, the default response serializer has the same behavior of JSON
|
||||
/// response methods of version 2.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <param name="serializer">A <see cref="ResponseSerializerCallback"/> used to prepare the response.</param>
|
||||
/// <param name="data">The data to serialize.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="serializer"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="SendDataAsync(IHttpContext,ResponseSerializerCallback,object)"/>
|
||||
/// <seealso cref="ResponseSerializer.Default"/>
|
||||
public static Task SendDataAsync(this IHttpContext @this, ResponseSerializerCallback serializer, object data)
|
||||
=> Validate.NotNull(nameof(serializer), serializer)(@this, data);
|
||||
}
|
||||
}
|
||||
30
Vendor/EmbedIO-3.5.2/HttpContextExtensions.cs
vendored
Normal file
30
Vendor/EmbedIO-3.5.2/HttpContextExtensions.cs
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for types implementing <see cref="IHttpContext"/>.
|
||||
/// </summary>
|
||||
public static partial class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Gets the underlying <see cref="IHttpContextImpl"/> interface of an <see cref="IHttpContext"/>.</para>
|
||||
/// <para>This API mainly supports the EmbedIO infrastructure; it is not intended to be used directly from your code,
|
||||
/// unless to fulfill very specific needs in the development of plug-ins (modules, etc.) for EmbedIO.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <returns>The underlying <see cref="IHttpContextImpl"/> interface representing
|
||||
/// the HTTP context implementation.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="this"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="EmbedIOInternalErrorException">
|
||||
/// <paramref name="this"/> does not implement <see cref="IHttpContextImpl"/>.
|
||||
/// </exception>
|
||||
public static IHttpContextImpl GetImplementation(this IHttpContext @this)
|
||||
=> Validate.NotNull(nameof(@this), @this) as IHttpContextImpl
|
||||
?? throw SelfCheck.Failure($"{@this.GetType().FullName} does not implement {nameof(IHttpContextImpl)}.");
|
||||
}
|
||||
}
|
||||
158
Vendor/EmbedIO-3.5.2/HttpException-Shortcuts.cs
vendored
Normal file
158
Vendor/EmbedIO-3.5.2/HttpException-Shortcuts.cs
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpException
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException" /> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>500 Internal Server Error</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>
|
||||
/// A newly-created <see cref="HttpException" />.
|
||||
/// </returns>
|
||||
public static HttpException InternalServerError(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.InternalServerError, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException" /> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>401 Unauthorized</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>
|
||||
/// A newly-created <see cref="HttpException" />.
|
||||
/// </returns>
|
||||
public static HttpException Unauthorized(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.Unauthorized, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>403 Forbidden</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>A newly-created <see cref="HttpException"/>.</returns>
|
||||
public static HttpException Forbidden(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.Forbidden, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>400 Bad Request</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>A newly-created <see cref="HttpException"/>.</returns>
|
||||
public static HttpException BadRequest(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.BadRequest, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>404 Not Found</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>A newly-created <see cref="HttpException"/>.</returns>
|
||||
public static HttpException NotFound(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.NotFound, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>405 Method Not Allowed</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>A newly-created <see cref="HttpException"/>.</returns>
|
||||
public static HttpException MethodNotAllowed(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.MethodNotAllowed, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpNotAcceptableException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>406 Not Acceptable</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <returns>A newly-created <see cref="HttpNotAcceptableException"/>.</returns>
|
||||
/// <seealso cref="HttpNotAcceptableException()"/>
|
||||
public static HttpNotAcceptableException NotAcceptable() => new HttpNotAcceptableException();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Returns a new instance of <see cref="HttpNotAcceptableException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>406 Not Acceptable</c>
|
||||
/// response to the client.</para>
|
||||
/// </summary>
|
||||
/// <param name="vary">A value, or a comma-separated list of values, to set the response's <c>Vary</c> header to.</param>
|
||||
/// <returns>A newly-created <see cref="HttpNotAcceptableException"/>.</returns>
|
||||
/// <seealso cref="HttpNotAcceptableException(string)"/>
|
||||
public static HttpNotAcceptableException NotAcceptable(string vary) => new HttpNotAcceptableException(vary);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpRangeNotSatisfiableException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>416 Range Not Satisfiable</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <returns>A newly-created <see cref="HttpRangeNotSatisfiableException"/>.</returns>
|
||||
/// <seealso cref="HttpRangeNotSatisfiableException()"/>
|
||||
public static HttpRangeNotSatisfiableException RangeNotSatisfiable() => new HttpRangeNotSatisfiableException();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpRangeNotSatisfiableException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>416 Range Not Satisfiable</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="contentLength">The total length of the requested resource, expressed in bytes,
|
||||
/// or <see langword="null"/> to omit the <c>Content-Range</c> header in the response.</param>
|
||||
/// <returns>A newly-created <see cref="HttpRangeNotSatisfiableException"/>.</returns>
|
||||
/// <seealso cref="HttpRangeNotSatisfiableException()"/>
|
||||
public static HttpRangeNotSatisfiableException RangeNotSatisfiable(long? contentLength)
|
||||
=> new HttpRangeNotSatisfiableException(contentLength);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpRedirectException" /> that, when thrown,
|
||||
/// will break the request handling control flow and redirect the client
|
||||
/// to the specified location, using response status code 302.
|
||||
/// </summary>
|
||||
/// <param name="location">The redirection target.</param>
|
||||
/// <returns>
|
||||
/// A newly-created <see cref="HttpRedirectException" />.
|
||||
/// </returns>
|
||||
public static HttpRedirectException Redirect(string location)
|
||||
=> new HttpRedirectException(location);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpRedirectException" /> that, when thrown,
|
||||
/// will break the request handling control flow and redirect the client
|
||||
/// to the specified location, using the specified response status code.
|
||||
/// </summary>
|
||||
/// <param name="location">The redirection target.</param>
|
||||
/// <param name="statusCode">The status code to set on the response, in the range from 300 to 399.</param>
|
||||
/// <returns>
|
||||
/// A newly-created <see cref="HttpRedirectException" />.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="statusCode"/> is not in the 300-399 range.</exception>
|
||||
public static HttpRedirectException Redirect(string location, int statusCode)
|
||||
=> new HttpRedirectException(location, statusCode);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpRedirectException" /> that, when thrown,
|
||||
/// will break the request handling control flow and redirect the client
|
||||
/// to the specified location, using the specified response status code.
|
||||
/// </summary>
|
||||
/// <param name="location">The redirection target.</param>
|
||||
/// <param name="statusCode">One of the redirection status codes, to be set on the response.</param>
|
||||
/// <returns>
|
||||
/// A newly-created <see cref="HttpRedirectException" />.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="statusCode"/> is not a redirection status code.</exception>
|
||||
public static HttpRedirectException Redirect(string location, HttpStatusCode statusCode)
|
||||
=> new HttpRedirectException(location, statusCode);
|
||||
}
|
||||
}
|
||||
105
Vendor/EmbedIO-3.5.2/HttpException.cs
vendored
Normal file
105
Vendor/EmbedIO-3.5.2/HttpException.cs
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// When thrown, breaks the request handling control flow
|
||||
/// and sends an error response to the client.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1032 // Implement standard exception constructors - they have no meaning here.
|
||||
public partial class HttpException : Exception, IHttpException
|
||||
#pragma warning restore CA1032
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException"/> class,
|
||||
/// with no message to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
public HttpException(int statusCode)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException"/> class,
|
||||
/// with no message to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
public HttpException(HttpStatusCode statusCode)
|
||||
: this((int)statusCode)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException"/> class,
|
||||
/// with a message to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
/// <param name="message">A message to include in the response as plain text.</param>
|
||||
public HttpException(int statusCode, string? message)
|
||||
: base(message)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
HttpExceptionMessage = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException"/> class,
|
||||
/// with a message to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
/// <param name="message">A message to include in the response as plain text.</param>
|
||||
public HttpException(HttpStatusCode statusCode, string? message)
|
||||
: this((int)statusCode, message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException" /> class,
|
||||
/// with a message and a data object to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
/// <param name="message">A message to include in the response as plain text.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
public HttpException(int statusCode, string? message, object? data)
|
||||
: this(statusCode, message)
|
||||
{
|
||||
DataObject = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException" /> class,
|
||||
/// with a message and a data object to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
/// <param name="message">A message to include in the response as plain text.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
public HttpException(HttpStatusCode statusCode, string? message, object? data)
|
||||
: this((int)statusCode, message, data)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int StatusCode { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object? DataObject { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
string? IHttpException.Message => HttpExceptionMessage;
|
||||
|
||||
// This property is necessary because when an exception with a null Message is thrown
|
||||
// the CLR provides a standard message. We want null to remain null in IHttpException.
|
||||
private string? HttpExceptionMessage { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// <para>This method does nothing; there is no need to call
|
||||
/// <c>base.PrepareResponse</c> in overrides of this method.</para>
|
||||
/// </remarks>
|
||||
public virtual void PrepareResponse(IHttpContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
153
Vendor/EmbedIO-3.5.2/HttpExceptionHandler.cs
vendored
Normal file
153
Vendor/EmbedIO-3.5.2/HttpExceptionHandler.cs
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides standard handlers for HTTP exceptions at both module and server level.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Where applicable, HTTP exception handlers defined in this class
|
||||
/// use the <see cref="ExceptionHandler.ContactInformation"/> and
|
||||
/// <see cref="ExceptionHandler.IncludeStackTraces"/> properties to customize
|
||||
/// their behavior.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="IWebServer.OnHttpException"/>
|
||||
/// <seealso cref="IWebModule.OnHttpException"/>
|
||||
public static class HttpExceptionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Gets the default handler used by <see cref="WebServerBase{TOptions}"/>.</para>
|
||||
/// <para>This is the same as <see cref="HtmlResponse"/>.</para>
|
||||
/// </summary>
|
||||
public static HttpExceptionHandlerCallback Default { get; } = HtmlResponse;
|
||||
|
||||
/// <summary>
|
||||
/// Sends an empty response.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="httpException">The HTTP exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
#pragma warning disable CA1801 // Unused parameter
|
||||
public static Task EmptyResponse(IHttpContext context, IHttpException httpException)
|
||||
#pragma warning restore CA1801
|
||||
=> Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// <para>Sends a HTTP exception's <see cref="IHttpException.Message">Message</see> property
|
||||
/// as a plain text response.</para>
|
||||
/// <para>This handler does not use the <see cref="IHttpException.DataObject">DataObject</see> property.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="httpException">The HTTP exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
public static Task PlainTextResponse(IHttpContext context, IHttpException httpException)
|
||||
=> context.SendStringAsync(httpException.Message ?? string.Empty, MimeType.PlainText, WebServer.DefaultEncoding);
|
||||
|
||||
/// <summary>
|
||||
/// <para>Sends a response with a HTML payload
|
||||
/// briefly describing the error, including contact information and/or a stack trace
|
||||
/// if specified via the <see cref="ExceptionHandler.ContactInformation"/>
|
||||
/// and <see cref="ExceptionHandler.IncludeStackTraces"/> properties, respectively.</para>
|
||||
/// <para>This handler does not use the <see cref="IHttpException.DataObject">DataObject</see> property.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="httpException">The HTTP exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
public static Task HtmlResponse(IHttpContext context, IHttpException httpException)
|
||||
=> context.SendStandardHtmlAsync(
|
||||
httpException.StatusCode,
|
||||
text => {
|
||||
text.Write(
|
||||
"<p><strong>Exception type:</strong> {0}<p><strong>Message:</strong> {1}",
|
||||
WebUtility.HtmlEncode(httpException.GetType().FullName ?? "<unknown>"),
|
||||
WebUtility.HtmlEncode(httpException.Message));
|
||||
|
||||
text.Write("<hr><p>If this error is completely unexpected to you, and you think you should not seeing this page, please contact the server administrator");
|
||||
|
||||
if (!string.IsNullOrEmpty(ExceptionHandler.ContactInformation))
|
||||
text.Write(" ({0})", WebUtility.HtmlEncode(ExceptionHandler.ContactInformation));
|
||||
|
||||
text.Write(", informing them of the time this error occurred and the action(s) you performed that resulted in this error.</p>");
|
||||
|
||||
if (ExceptionHandler.IncludeStackTraces)
|
||||
{
|
||||
text.Write(
|
||||
"</p><p><strong>Stack trace:</strong></p><br><pre>{0}</pre>",
|
||||
WebUtility.HtmlEncode(httpException.StackTrace));
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets a <see cref="HttpExceptionHandlerCallback" /> that will serialize a HTTP exception's
|
||||
/// <see cref="IHttpException.DataObject">DataObject</see> property and send it as a JSON response.</para>
|
||||
/// </summary>
|
||||
/// <param name="serializerCallback">A <see cref="ResponseSerializerCallback" /> used to serialize data and send it to the client.</param>
|
||||
/// <returns>A <see cref="HttpExceptionHandlerCallback" />.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="serializerCallback"/> is <see langword="null"/>.</exception>
|
||||
public static HttpExceptionHandlerCallback DataResponse(ResponseSerializerCallback serializerCallback)
|
||||
{
|
||||
Validate.NotNull(nameof(serializerCallback), serializerCallback);
|
||||
|
||||
return (context, httpException) => serializerCallback(context, httpException.DataObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets a <see cref="HttpExceptionHandlerCallback" /> that will serialize a HTTP exception's
|
||||
/// <see cref="IHttpException.Message">Message</see> and <see cref="IHttpException.DataObject">DataObject</see> properties
|
||||
/// and send them as a JSON response.</para>
|
||||
/// <para>The response will be a JSON object with a <c>message</c> property and a <c>data</c> property.</para>
|
||||
/// </summary>
|
||||
/// <param name="serializerCallback">A <see cref="ResponseSerializerCallback" /> used to serialize data and send it to the client.</param>
|
||||
/// <returns>A <see cref="HttpExceptionHandlerCallback" />.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="serializerCallback"/> is <see langword="null"/>.</exception>
|
||||
public static HttpExceptionHandlerCallback FullDataResponse(ResponseSerializerCallback serializerCallback)
|
||||
{
|
||||
Validate.NotNull(nameof(serializerCallback), serializerCallback);
|
||||
|
||||
return (context, httpException) => serializerCallback(context, new
|
||||
{
|
||||
message = httpException.Message,
|
||||
data = httpException.DataObject,
|
||||
});
|
||||
}
|
||||
|
||||
internal static async Task Handle(string logSource, IHttpContext context, Exception exception, HttpExceptionHandlerCallback? handler)
|
||||
{
|
||||
if (handler == null || !(exception is IHttpException httpException))
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(exception).Throw();
|
||||
return;
|
||||
}
|
||||
|
||||
exception.Log(logSource, $"[{context.Id}] HTTP exception {httpException.StatusCode}");
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.SetEmptyResponse(httpException.StatusCode);
|
||||
context.Response.DisableCaching();
|
||||
httpException.PrepareResponse(context);
|
||||
await handler(context, httpException)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (context.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception2)
|
||||
{
|
||||
exception2.Log(logSource, $"[{context.Id}] Unhandled exception while handling HTTP exception {httpException.StatusCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Vendor/EmbedIO-3.5.2/HttpExceptionHandlerCallback.cs
vendored
Normal file
21
Vendor/EmbedIO-3.5.2/HttpExceptionHandlerCallback.cs
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// A callback used to build the contents of the response for an <see cref="IHttpException" />.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="httpException">An <see cref="IHttpException" /> interface.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
/// <remarks>
|
||||
/// <para>When this delegate is called, the response's status code has already been set and the <see cref="IHttpException.PrepareResponse"/>
|
||||
/// method has already been called. The only thing left to do is preparing the response's content, according
|
||||
/// to the <see cref="IHttpException.Message"/> property.</para>
|
||||
/// <para>Any exception thrown by a handler (even a HTTP exception) will go unhandled: the web server
|
||||
/// will not crash, but processing of the request will be aborted, and the response will be flushed as-is.
|
||||
/// In other words, it is not a good ides to <c>throw HttpException.NotFound()</c> (or similar)
|
||||
/// from a handler.</para>
|
||||
/// </remarks>
|
||||
public delegate Task HttpExceptionHandlerCallback(IHttpContext context, IHttpException httpException);
|
||||
}
|
||||
449
Vendor/EmbedIO-3.5.2/HttpHeaderNames.cs
vendored
Normal file
449
Vendor/EmbedIO-3.5.2/HttpHeaderNames.cs
vendored
Normal file
@@ -0,0 +1,449 @@
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes known HTTP header names.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The constants in this class have been extracted from a list of known HTTP header names.
|
||||
/// The presence of a header name in this class is not a guarantee that EmbedIO supports,
|
||||
/// or even recognizes, it. Refer to the documentation for each module for information about supported
|
||||
/// headers.</para>
|
||||
/// </remarks>
|
||||
public static class HttpHeaderNames
|
||||
{
|
||||
// The .NET Core sources were taken as reference for this list of constants.
|
||||
// See https://github.com/dotnet/corefx/blob/master/src/Common/src/System/Net/HttpKnownHeaderNames.cs
|
||||
// However, not all constants come from there, so be careful not to copy-paste indiscriminately.
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Accept = "Accept";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept-Charset</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AcceptCharset = "Accept-Charset";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept-Encoding</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AcceptEncoding = "Accept-Encoding";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept-Language</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AcceptLanguage = "Accept-Language";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept-Patch</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AcceptPatch = "Accept-Patch";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept-Ranges</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AcceptRanges = "Accept-Ranges";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Allow-Credentials</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Allow-Headers</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Allow-Methods</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Allow-Origin</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Expose-Headers</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Max-Age</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlMaxAge = "Access-Control-Max-Age";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Request-Headers</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Request-Method</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlRequestMethod = "Access-Control-Request-Method";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Age</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Age = "Age";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Allow</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Allow = "Allow";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Alt-Svc</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AltSvc = "Alt-Svc";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Authorization</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Authorization = "Authorization";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Cache-Control</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string CacheControl = "Cache-Control";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Connection</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Connection = "Connection";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Disposition</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentDisposition = "Content-Disposition";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Encoding</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentEncoding = "Content-Encoding";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Language</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentLanguage = "Content-Language";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Length</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentLength = "Content-Length";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Location</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentLocation = "Content-Location";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-MD5</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentMD5 = "Content-MD5";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Range</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentRange = "Content-Range";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Security-Policy</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentSecurityPolicy = "Content-Security-Policy";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Type</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentType = "Content-Type";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Cookie</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Cookie = "Cookie";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Cookie2</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Cookie2 = "Cookie2";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Date</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Date = "Date";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>ETag</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ETag = "ETag";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Expect</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Expect = "Expect";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Expires</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Expires = "Expires";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>From</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string From = "From";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Host</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Host = "Host";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>If-Match</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string IfMatch = "If-Match";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>If-Modified-Since</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string IfModifiedSince = "If-Modified-Since";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>If-None-Match</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string IfNoneMatch = "If-None-Match";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>If-Range</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string IfRange = "If-Range";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>If-Unmodified-Since</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string IfUnmodifiedSince = "If-Unmodified-Since";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Keep-Alive</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string KeepAlive = "Keep-Alive";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Last-Modified</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string LastModified = "Last-Modified";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Link</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Link = "Link";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Location</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Location = "Location";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Max-Forwards</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string MaxForwards = "Max-Forwards";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Origin</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Origin = "Origin";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>P3P</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string P3P = "P3P";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Pragma</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Pragma = "Pragma";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Proxy-Authenticate</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ProxyAuthenticate = "Proxy-Authenticate";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Proxy-Authorization</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ProxyAuthorization = "Proxy-Authorization";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Proxy-Connection</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ProxyConnection = "Proxy-Connection";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Public-Key-Pins</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string PublicKeyPins = "Public-Key-Pins";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Range</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Range = "Range";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Referer</c> HTTP header.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The incorrect spelling ("Referer" instead of "Referrer") is intentional
|
||||
/// and has historical reasons.</para>
|
||||
/// <para>See the "Etymology" section of <a href="https://en.wikipedia.org/wiki/HTTP_referer">the Wikipedia article</a>
|
||||
/// on this header for more information.</para>
|
||||
/// </remarks>
|
||||
public const string Referer = "Referer";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Retry-After</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string RetryAfter = "Retry-After";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Sec-WebSocket-Accept</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SecWebSocketAccept = "Sec-WebSocket-Accept";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Sec-WebSocket-Extensions</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Sec-WebSocket-Key</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SecWebSocketKey = "Sec-WebSocket-Key";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Sec-WebSocket-Protocol</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Sec-WebSocket-Version</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SecWebSocketVersion = "Sec-WebSocket-Version";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Server</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Server = "Server";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Set-Cookie</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SetCookie = "Set-Cookie";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Set-Cookie2</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SetCookie2 = "Set-Cookie2";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Strict-Transport-Security</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string StrictTransportSecurity = "Strict-Transport-Security";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>TE</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string TE = "TE";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>TSV</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string TSV = "TSV";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Trailer</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Trailer = "Trailer";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Transfer-Encoding</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string TransferEncoding = "Transfer-Encoding";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Upgrade</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Upgrade = "Upgrade";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Upgrade-Insecure-Requests</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>User-Agent</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string UserAgent = "User-Agent";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Vary</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Vary = "Vary";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Via</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Via = "Via";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>WWW-Authenticate</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string WWWAuthenticate = "WWW-Authenticate";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Warning</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Warning = "Warning";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-AspNet-Version</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XAspNetVersion = "X-AspNet-Version";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-Content-Duration</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XContentDuration = "X-Content-Duration";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-Content-Type-Options</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XContentTypeOptions = "X-Content-Type-Options";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-Frame-Options</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XFrameOptions = "X-Frame-Options";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-MSEdge-Ref</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XMSEdgeRef = "X-MSEdge-Ref";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-Powered-By</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XPoweredBy = "X-Powered-By";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-Request-ID</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XRequestID = "X-Request-ID";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-UA-Compatible</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XUACompatible = "X-UA-Compatible";
|
||||
}
|
||||
}
|
||||
20
Vendor/EmbedIO-3.5.2/HttpListenerMode.cs
vendored
Normal file
20
Vendor/EmbedIO-3.5.2/HttpListenerMode.cs
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the HTTP listeners available for use in a <see cref="WebServer"/>.
|
||||
/// </summary>
|
||||
public enum HttpListenerMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Use EmbedIO's internal HTTP listener implementation,
|
||||
/// based on Mono's <c>System.Net.HttpListener</c>.
|
||||
/// </summary>
|
||||
EmbedIO,
|
||||
|
||||
/// <summary>
|
||||
/// Use the <see cref="System.Net.HttpListener"/> class
|
||||
/// provided by the .NET runtime in use.
|
||||
/// </summary>
|
||||
Microsoft,
|
||||
}
|
||||
}
|
||||
55
Vendor/EmbedIO-3.5.2/HttpNotAcceptableException.cs
vendored
Normal file
55
Vendor/EmbedIO-3.5.2/HttpNotAcceptableException.cs
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// When thrown, breaks the request handling control flow
|
||||
/// and sends a redirection response to the client.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1032 // Implement standard exception constructors - they have no meaning here.
|
||||
public class HttpNotAcceptableException : HttpException
|
||||
#pragma warning restore CA1032
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpNotAcceptableException"/> class,
|
||||
/// without specifying a value for the response's <c>Vary</c> header.
|
||||
/// </summary>
|
||||
public HttpNotAcceptableException()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpNotAcceptableException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="vary">
|
||||
/// <para>A value, or a comma-separated list of values, to set the response's <c>Vary</c> header to.</para>
|
||||
/// <para>Although not specified in <see href="https://tools.ietf.org/html/rfc7231#section-6.5.6">RFC7231</see>,
|
||||
/// this may help the client to understand why the request has been rejected.</para>
|
||||
/// <para>If this parameter is <see langword="null"/> or the empty string, the response's <c>Vary</c> header
|
||||
/// is not set.</para>
|
||||
/// </param>
|
||||
public HttpNotAcceptableException(string? vary)
|
||||
: base((int)HttpStatusCode.NotAcceptable)
|
||||
{
|
||||
Vary = string.IsNullOrEmpty(vary) ? null : vary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value, or comma-separated list of values, to be set
|
||||
/// on the response's <c>Vary</c> header.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If the empty string has been passed to the <see cref="HttpNotAcceptableException(string)"/>
|
||||
/// constructor, the value of this property is <see langword="null"/>.</para>
|
||||
/// </remarks>
|
||||
public string? Vary { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PrepareResponse(IHttpContext context)
|
||||
{
|
||||
if (Vary != null)
|
||||
context.Response.Headers.Add(HttpHeaderNames.Vary, Vary);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Vendor/EmbedIO-3.5.2/HttpRangeNotSatisfiableException.cs
vendored
Normal file
50
Vendor/EmbedIO-3.5.2/HttpRangeNotSatisfiableException.cs
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// When thrown, breaks the request handling control flow
|
||||
/// and sends a redirection response to the client.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1032 // Implement standard exception constructors - they have no meaning here.
|
||||
public class HttpRangeNotSatisfiableException : HttpException
|
||||
#pragma warning restore CA1032
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRangeNotSatisfiableException"/> class.
|
||||
/// without specifying a value for the response's <c>Content-Range</c> header.
|
||||
/// </summary>
|
||||
public HttpRangeNotSatisfiableException()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRangeNotSatisfiableException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="contentLength">The total length of the requested resource, expressed in bytes,
|
||||
/// or <see langword="null"/> to omit the <c>Content-Range</c> header in the response.</param>
|
||||
public HttpRangeNotSatisfiableException(long? contentLength)
|
||||
: base((int)HttpStatusCode.RequestedRangeNotSatisfiable)
|
||||
{
|
||||
ContentLength = contentLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total content length to be specified
|
||||
/// on the response's <c>Content-Range</c> header.
|
||||
/// </summary>
|
||||
public long? ContentLength { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PrepareResponse(IHttpContext context)
|
||||
{
|
||||
// RFC 7233, Section 3.1: "When this status code is generated in response
|
||||
// to a byte-range request, the sender
|
||||
// SHOULD generate a Content-Range header field specifying
|
||||
// the current length of the selected representation."
|
||||
if (ContentLength.HasValue)
|
||||
context.Response.Headers.Set(HttpHeaderNames.ContentRange, $"bytes */{ContentLength.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Vendor/EmbedIO-3.5.2/HttpRedirectException.cs
vendored
Normal file
54
Vendor/EmbedIO-3.5.2/HttpRedirectException.cs
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// When thrown, breaks the request handling control flow
|
||||
/// and sends a redirection response to the client.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1032 // Implement standard exception constructors - they have no meaning here.
|
||||
public class HttpRedirectException : HttpException
|
||||
#pragma warning restore CA1032
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRedirectException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="location">The redirection target.</param>
|
||||
/// <param name="statusCode">
|
||||
/// <para>The status code to set on the response, in the range from 300 to 399.</para>
|
||||
/// <para>By default, status code 302 (<c>Found</c>) is used.</para>
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentException"><paramref name="statusCode"/> is not in the 300-399 range.</exception>
|
||||
public HttpRedirectException(string location, int statusCode = (int)HttpStatusCode.Found)
|
||||
: base(statusCode)
|
||||
{
|
||||
if (statusCode < 300 || statusCode > 399)
|
||||
throw new ArgumentException("Redirect status code is not valid.", nameof(statusCode));
|
||||
|
||||
Location = location;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRedirectException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="location">The redirection target.</param>
|
||||
/// <param name="statusCode">One of the redirection status codes, to be set on the response.</param>
|
||||
/// <exception cref="ArgumentException"><paramref name="statusCode"/> is not a redirection status code.</exception>
|
||||
public HttpRedirectException(string location, HttpStatusCode statusCode)
|
||||
: this(location, (int)statusCode)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL where the client will be redirected.
|
||||
/// </summary>
|
||||
public string Location { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PrepareResponse(IHttpContext context)
|
||||
{
|
||||
context.Redirect(Location, StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
262
Vendor/EmbedIO-3.5.2/HttpRequestExtensions.cs
vendored
Normal file
262
Vendor/EmbedIO-3.5.2/HttpRequestExtensions.cs
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for types implementing <see cref="IHttpRequest"/>.
|
||||
/// </summary>
|
||||
public static class HttpRequestExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Returns a string representing the remote IP address and port of an <see cref="IHttpRequest"/> interface.</para>
|
||||
/// <para>This method can be called even on a <see langword="null"/> interface, or one that has no
|
||||
/// remote end point, or no remote address; it will always return a non-<see langword="null"/>,
|
||||
/// non-empty string.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpRequest"/> on which this method is called.</param>
|
||||
/// <returns>
|
||||
/// If <paramref name="this"/> is <see langword="null"/>, or its <see cref="IHttpRequest.RemoteEndPoint">RemoteEndPoint</see>
|
||||
/// is <see langword="null"/>, the string <c>"<null></c>; otherwise, the remote end point's
|
||||
/// <see cref="IPEndPoint.Address">Address</see> (or the string <c>"<???>"</c> if it is <see langword="null"/>)
|
||||
/// followed by a colon and the <see cref="IPEndPoint.Port">Port</see> number.
|
||||
/// </returns>
|
||||
public static string SafeGetRemoteEndpointStr(this IHttpRequest @this)
|
||||
{
|
||||
var endPoint = @this?.RemoteEndPoint;
|
||||
return endPoint == null
|
||||
? "<null>"
|
||||
: $"{endPoint.Address?.ToString() ?? "<???>"}:{endPoint.Port.ToString(CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Attempts to proactively negotiate a compression method for a response,
|
||||
/// based on a request's <c>Accept-Encoding</c> header (or lack of it).</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpRequest"/> on which this method is called.</param>
|
||||
/// <param name="preferCompression"><see langword="true"/> if sending compressed data is preferred over
|
||||
/// sending non-compressed data; otherwise, <see langword="false"/>.</param>
|
||||
/// <param name="compressionMethod">When this method returns, the compression method to use for the response,
|
||||
/// if content negotiation is successful. This parameter is passed uninitialized.</param>
|
||||
/// <param name="prepareResponse">When this method returns, a callback that prepares data in an <see cref="IHttpResponse"/>
|
||||
/// according to the result of content negotiation. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if content negotiation is successful;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If this method returns <see langword="true"/>, the <paramref name="prepareResponse"/> callback
|
||||
/// will set appropriate response headers to reflect the results of content negotiation.</para>
|
||||
/// <para>If this method returns <see langword="false"/>, the <paramref name="prepareResponse"/> callback
|
||||
/// will throw a <see cref="HttpNotAcceptableException"/> to send a <c>406 Not Acceptable</c> response
|
||||
/// with the <c>Vary</c> header set to <c>Accept-Encoding</c>,
|
||||
/// so that the client may know the reason why the request has been rejected.</para>
|
||||
/// <para>If <paramref name="this"/> has no<c>Accept-Encoding</c> header, this method
|
||||
/// always returns <see langword="true"/> and sets <paramref name="compressionMethod"/>
|
||||
/// to <see cref="CompressionMethod.None"/>.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="HttpNotAcceptableException(string)"/>
|
||||
public static bool TryNegotiateContentEncoding(
|
||||
this IHttpRequest @this,
|
||||
bool preferCompression,
|
||||
out CompressionMethod compressionMethod,
|
||||
out Action<IHttpResponse> prepareResponse)
|
||||
{
|
||||
var acceptedEncodings = new QValueList(true, @this.Headers.GetValues(HttpHeaderNames.AcceptEncoding));
|
||||
if (!acceptedEncodings.TryNegotiateContentEncoding(preferCompression, out compressionMethod, out var compressionMethodName))
|
||||
{
|
||||
prepareResponse = r => throw HttpException.NotAcceptable(HttpHeaderNames.AcceptEncoding);
|
||||
return false;
|
||||
}
|
||||
|
||||
prepareResponse = r => {
|
||||
r.Headers.Add(HttpHeaderNames.Vary, HttpHeaderNames.AcceptEncoding);
|
||||
r.Headers.Set(HttpHeaderNames.ContentEncoding, compressionMethodName);
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Checks whether an <c>If-None-Match</c> header exists in a request
|
||||
/// and, if so, whether it contains a given entity tag.</para>
|
||||
/// <para>See <see href="https://tools.ietf.org/html/rfc7232#section-3.2">RFC7232, Section 3.2</see>
|
||||
/// for a normative reference; however, see the Remarks section for more information
|
||||
/// about the RFC compliance of this method.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpRequest"/> on which this method is called.</param>
|
||||
/// <param name="entityTag">The entity tag.</param>
|
||||
/// <param name="headerExists">When this method returns, a value that indicates whether an
|
||||
/// <c>If-None-Match</c> header is present in <paramref name="this"/>, regardless of the method's
|
||||
/// return value. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if an <c>If-None-Match</c> header is present in
|
||||
/// <paramref name="this"/> and one of the entity tags listed in it is equal to <paramref name="entityTag"/>;
|
||||
/// <see langword="false"/> otherwise.</returns>
|
||||
/// <remarks>
|
||||
/// <para><see href="https://tools.ietf.org/html/rfc7232#section-3.2">RFC7232, Section 3.2</see>
|
||||
/// states that a weak comparison function (as defined in
|
||||
/// <see href="https://tools.ietf.org/html/rfc7232#section-2.3.2">RFC7232, Section 2.3.2</see>)
|
||||
/// must be used for <c>If-None-Match</c>. That would mean parsing every entity tag, at least minimally,
|
||||
/// to determine whether it is a "weak" or "strong" tag. Since EmbedIO currently generates only
|
||||
/// "strong" tags, this method uses the default string comparer instead.</para>
|
||||
/// <para>The behavior of this method is thus not, strictly speaking, RFC7232-compliant;
|
||||
/// it works, though, with entity tags generated by EmbedIO.</para>
|
||||
/// </remarks>
|
||||
public static bool CheckIfNoneMatch(this IHttpRequest @this, string entityTag, out bool headerExists)
|
||||
{
|
||||
var values = @this.Headers.GetValues(HttpHeaderNames.IfNoneMatch);
|
||||
if (values == null)
|
||||
{
|
||||
headerExists = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
headerExists = true;
|
||||
return values.Select(t => t.Trim()).Contains(entityTag);
|
||||
}
|
||||
|
||||
// Check whether the If-Modified-Since request header exists
|
||||
// and specifies a date and time more recent than or equal to
|
||||
// the date and time of last modification of the requested resource.
|
||||
// RFC7232, Section 3.3
|
||||
|
||||
/// <summary>
|
||||
/// <para>Checks whether an <c>If-Modified-Since</c> header exists in a request
|
||||
/// and, if so, whether its value is a date and time more recent or equal to
|
||||
/// a given <see cref="DateTime"/>.</para>
|
||||
/// <para>See <see href="https://tools.ietf.org/html/rfc7232#section-3.3">RFC7232, Section 3.3</see>
|
||||
/// for a normative reference.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpRequest"/> on which this method is called.</param>
|
||||
/// <param name="lastModifiedUtc">A date and time value, in Coordinated Universal Time,
|
||||
/// expressing the last time a resource was modified.</param>
|
||||
/// <param name="headerExists">When this method returns, a value that indicates whether an
|
||||
/// <c>If-Modified-Since</c> header is present in <paramref name="this"/>, regardless of the method's
|
||||
/// return value. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if an <c>If-Modified-Since</c> header is present in
|
||||
/// <paramref name="this"/> and its value is a date and time more recent or equal to <paramref name="lastModifiedUtc"/>;
|
||||
/// <see langword="false"/> otherwise.</returns>
|
||||
public static bool CheckIfModifiedSince(this IHttpRequest @this, DateTime lastModifiedUtc, out bool headerExists)
|
||||
{
|
||||
var value = @this.Headers.Get(HttpHeaderNames.IfModifiedSince);
|
||||
if (value == null)
|
||||
{
|
||||
headerExists = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
headerExists = true;
|
||||
return HttpDate.TryParse(value, out var dateTime)
|
||||
&& dateTime.UtcDateTime >= lastModifiedUtc;
|
||||
}
|
||||
|
||||
// Checks the Range request header to tell whether to send
|
||||
// a "206 Partial Content" response.
|
||||
|
||||
/// <summary>
|
||||
/// <para>Checks whether a <c>Range</c> header exists in a request
|
||||
/// and, if so, determines whether it is possible to send a <c>206 Partial Content</c> response.</para>
|
||||
/// <para>See <see href="https://tools.ietf.org/html/rfc7233">RFC7233</see>
|
||||
/// for a normative reference; however, see the Remarks section for more information
|
||||
/// about the RFC compliance of this method.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpRequest"/> on which this method is called.</param>
|
||||
/// <param name="contentLength">The total length, in bytes, of the response entity, i.e.
|
||||
/// what would be sent in a <c>200 OK</c> response.</param>
|
||||
/// <param name="entityTag">An entity tag representing the response entity. This value is checked against
|
||||
/// the <c>If-Range</c> header, if it is present.</param>
|
||||
/// <param name="lastModifiedUtc">The date and time value, in Coordinated Universal Time,
|
||||
/// expressing the last modification time of the resource entity. This value is checked against
|
||||
/// the <c>If-Range</c> header, if it is present.</param>
|
||||
/// <param name="start">When this method returns <see langword="true"/>, the start of the requested byte range.
|
||||
/// This parameter is passed uninitialized.</param>
|
||||
/// <param name="upperBound">
|
||||
/// <para>When this method returns <see langword="true"/>, the upper bound of the requested byte range.
|
||||
/// This parameter is passed uninitialized.</para>
|
||||
/// <para>Note that the upper bound of a range is NOT the sum of the range's start and length;
|
||||
/// for example, a range expressed as <c>bytes=0-99</c> has a start of 0, an upper bound of 99,
|
||||
/// and a length of 100 bytes.</para>
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <para>This method returns <see langword="true"/> if the following conditions are satisfied:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>>the request's HTTP method is <c>GET</c>;</description></item>
|
||||
/// <item><description>>a <c>Range</c> header is present in the request;</description></item>
|
||||
/// <item><description>>either no <c>If-Range</c> header is present in the request, or it
|
||||
/// specifies an entity tag equal to <paramref name="entityTag"/>, or a UTC date and time
|
||||
/// equal to <paramref name="lastModifiedUtc"/>;</description></item>
|
||||
/// <item><description>>the <c>Range</c> header specifies exactly one range;</description></item>
|
||||
/// <item><description>>the specified range is entirely contained in the range from 0 to <paramref name="contentLength"/> - 1.</description></item>
|
||||
/// </list>
|
||||
/// <para>If the last condition is not satisfied, i.e. the specified range start and/or upper bound
|
||||
/// are out of the range from 0 to <paramref name="contentLength"/> - 1, this method does not return;
|
||||
/// it throws a <see cref="HttpRangeNotSatisfiableException"/> instead.</para>
|
||||
/// <para>If any of the other conditions are not satisfied, this method returns <see langword="false"/>.</para>
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>According to <see href="https://tools.ietf.org/html/rfc7233#section-3.1">RFC7233, Section 3.1</see>,
|
||||
/// there are several conditions under which a server may ignore or reject a range request; therefore,
|
||||
/// clients are (or should be) prepared to receive a <c>200 OK</c> response with the whole response
|
||||
/// entity instead of the requested range(s). For this reason, until the generation of
|
||||
/// <c>multipart/byteranges</c> responses is implemented in EmbedIO, this method will ignore
|
||||
/// range requests specifying more than one range, even if this behavior is not, strictly speaking,
|
||||
/// RFC7233-compliant.</para>
|
||||
/// <para>To make clients aware that range requests are accepted for a resource, every <c>200 OK</c>
|
||||
/// (or <c>304 Not Modified</c>) response for the same resource should include an <c>Accept-Ranges</c>
|
||||
/// header with the string <c>bytes</c> as value.</para>
|
||||
/// </remarks>
|
||||
public static bool IsRangeRequest(this IHttpRequest @this, long contentLength, string entityTag, DateTime lastModifiedUtc, out long start, out long upperBound)
|
||||
{
|
||||
start = 0;
|
||||
upperBound = contentLength - 1;
|
||||
|
||||
// RFC7233, Section 3.1:
|
||||
// "A server MUST ignore a Range header field received with a request method other than GET."
|
||||
if (@this.HttpVerb != HttpVerbs.Get)
|
||||
return false;
|
||||
|
||||
// No Range header, no partial content.
|
||||
var rangeHeader = @this.Headers.Get(HttpHeaderNames.Range);
|
||||
if (rangeHeader == null)
|
||||
return false;
|
||||
|
||||
// Ignore the Range header if there is no If-Range header
|
||||
// or if the If-Range header specifies a non-matching validator.
|
||||
// RFC7233, Section 3.2: "If the validator given in the If-Range header field matches the
|
||||
// current validator for the selected representation of the target
|
||||
// resource, then the server SHOULD process the Range header field as
|
||||
// requested.If the validator does not match, the server MUST ignore
|
||||
// the Range header field.Note that this comparison by exact match,
|
||||
// including when the validator is an HTTP-date, differs from the
|
||||
// "earlier than or equal to" comparison used when evaluating an
|
||||
// If-Unmodified-Since conditional."
|
||||
var ifRange = @this.Headers.Get(HttpHeaderNames.IfRange)?.Trim();
|
||||
if (ifRange != null && ifRange != entityTag)
|
||||
{
|
||||
if (!HttpDate.TryParse(ifRange, out var rangeDate))
|
||||
return false;
|
||||
|
||||
if (rangeDate.UtcDateTime != lastModifiedUtc)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore the Range request header if it cannot be parsed successfully.
|
||||
if (!RangeHeaderValue.TryParse(rangeHeader, out var range))
|
||||
return false;
|
||||
|
||||
// EmbedIO does not support multipart/byteranges responses (yet),
|
||||
// thus ignore range requests that specify one range.
|
||||
if (range.Ranges.Count != 1)
|
||||
return false;
|
||||
|
||||
var firstRange = range.Ranges.First();
|
||||
start = firstRange.From ?? 0L;
|
||||
upperBound = firstRange.To ?? contentLength - 1;
|
||||
if (start >= contentLength || upperBound < start || upperBound >= contentLength)
|
||||
throw HttpException.RangeNotSatisfiable(contentLength);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Vendor/EmbedIO-3.5.2/HttpResponseExtensions.cs
vendored
Normal file
43
Vendor/EmbedIO-3.5.2/HttpResponseExtensions.cs
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for types implementing <see cref="IHttpResponse"/>.
|
||||
/// </summary>
|
||||
public static class HttpResponseExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the necessary headers to disable caching of a response on the client side.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpResponse"/> interface on which this method is called.</param>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static void DisableCaching(this IHttpResponse @this)
|
||||
{
|
||||
var headers = @this.Headers;
|
||||
headers.Set(HttpHeaderNames.Expires, "Sat, 26 Jul 1997 05:00:00 GMT");
|
||||
headers.Set(HttpHeaderNames.LastModified, HttpDate.Format(DateTime.UtcNow));
|
||||
headers.Set(HttpHeaderNames.CacheControl, "no-store, no-cache, must-revalidate");
|
||||
headers.Add(HttpHeaderNames.Pragma, "no-cache");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares a standard response without a body for the specified status code.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpResponse"/> interface on which this method is called.</param>
|
||||
/// <param name="statusCode">The HTTP status code of the response.</param>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">There is no standard status description for <paramref name="statusCode"/>.</exception>
|
||||
public static void SetEmptyResponse(this IHttpResponse @this, int statusCode)
|
||||
{
|
||||
if (!HttpStatusDescription.TryGet(statusCode, out var statusDescription))
|
||||
throw new ArgumentException("Status code has no standard description.", nameof(statusCode));
|
||||
|
||||
@this.StatusCode = statusCode;
|
||||
@this.StatusDescription = statusDescription;
|
||||
@this.ContentType = MimeType.Default;
|
||||
@this.ContentEncoding = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
146
Vendor/EmbedIO-3.5.2/HttpStatusDescription.cs
vendored
Normal file
146
Vendor/EmbedIO-3.5.2/HttpStatusDescription.cs
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Provides standard HTTP status descriptions.</para>
|
||||
/// <para>Data contained in this class comes from the following sources:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc7231#section-6">RFC7231 Section 6</see> (HTTP/1.1 Semantics and Content)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc6585">RFC6585</see> (Additional HTTP Status Codes)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc2774#section-7">RFC2774 Section 7</see> (An HTTP Extension Framework)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc7540#section-9.1.2">RFC7540 Section 9.1.2</see> (HTTP/2)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc4918#section-11">RFC4918 Section 11</see> (WebDAV)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc5842#section-7">RFC5842 Section 7</see> (Binding Extensions to WebDAV)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc7538#section-3">RFC7538 Section 3</see> (HTTP Status Code 308)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc3229#section-10.4.1">RFC3229 Section 10.4.1</see> (Delta encoding in HTTP)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc8297#section-2">RFC8297 Section 2</see> (Early Hints)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc7725#section-3">RFC7725 Section 3</see> (HTTP-status-451)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc2295#section-8.1">RFC2295 Section 8.1</see> (Transparent Content Negotiation)</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static class HttpStatusDescription
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<int, string> Dictionary = new Dictionary<int, string> {
|
||||
{ 100, "Continue" },
|
||||
{ 101, "Switching Protocols" },
|
||||
{ 102, "Processing" },
|
||||
{ 103, "Early Hints" },
|
||||
{ 200, "OK" },
|
||||
{ 201, "Created" },
|
||||
{ 202, "Accepted" },
|
||||
{ 203, "Non-Authoritative Information" },
|
||||
{ 204, "No Content" },
|
||||
{ 205, "Reset Content" },
|
||||
{ 206, "Partial Content" },
|
||||
{ 207, "Multi-Status" },
|
||||
{ 208, "Already Reported" },
|
||||
{ 226, "IM Used" },
|
||||
{ 300, "Multiple Choices" },
|
||||
{ 301, "Moved Permanently" },
|
||||
{ 302, "Found" },
|
||||
{ 303, "See Other" },
|
||||
{ 304, "Not Modified" },
|
||||
{ 305, "Use Proxy" },
|
||||
{ 307, "Temporary Redirect" },
|
||||
{ 308, "Permanent Redirect" },
|
||||
{ 400, "Bad Request" },
|
||||
{ 401, "Unauthorized" },
|
||||
{ 402, "Payment Required" },
|
||||
{ 403, "Forbidden" },
|
||||
{ 404, "Not Found" },
|
||||
{ 405, "Method Not Allowed" },
|
||||
{ 406, "Not Acceptable" },
|
||||
{ 407, "Proxy Authentication Required" },
|
||||
{ 408, "Request Timeout" },
|
||||
{ 409, "Conflict" },
|
||||
{ 410, "Gone" },
|
||||
{ 411, "Length Required" },
|
||||
{ 412, "Precondition Failed" },
|
||||
{ 413, "Request Entity Too Large" },
|
||||
{ 414, "Request-Uri Too Long" },
|
||||
{ 415, "Unsupported Media Type" },
|
||||
{ 416, "Requested Range Not Satisfiable" },
|
||||
{ 417, "Expectation Failed" },
|
||||
{ 421, "Misdirected Request" },
|
||||
{ 422, "Unprocessable Entity" },
|
||||
{ 423, "Locked" },
|
||||
{ 424, "Failed Dependency" },
|
||||
{ 426, "Upgrade Required" },
|
||||
{ 428, "Precondition Required" },
|
||||
{ 429, "Too Many Requests" },
|
||||
{ 431, "Request Header Fields Too Large" },
|
||||
{ 451, "Unavailable For Legal Reasons" },
|
||||
{ 500, "Internal Server Error" },
|
||||
{ 501, "Not Implemented" },
|
||||
{ 502, "Bad Gateway" },
|
||||
{ 503, "Service Unavailable" },
|
||||
{ 504, "Gateway Timeout" },
|
||||
{ 505, "Http Version Not Supported" },
|
||||
{ 506, "Variant Also Negotiates" },
|
||||
{ 507, "Insufficient Storage" },
|
||||
{ 508, "Loop Detected" },
|
||||
{ 510, "Not Extended" },
|
||||
{ 511, "Network Authentication Required" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the standard status description for a <see cref="HttpStatusCode"/>.
|
||||
/// </summary>
|
||||
/// <param name="code">The HTTP status code for which the standard description
|
||||
/// is to be retrieved.</param>
|
||||
/// <param name="description">When this method returns, the standard HTTP status description
|
||||
/// for the specified <paramref name="code"/> if it was found, or <see langword="null"/>
|
||||
/// if it was not found. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if the specified <paramref name="code"/> was found
|
||||
/// in the list of HTTP status codes for which the standard description is known;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
/// <seealso cref="TryGet(int,out string)"/>
|
||||
/// <seealso cref="Get(HttpStatusCode)"/>
|
||||
public static bool TryGet(HttpStatusCode code, out string description) => Dictionary.TryGetValue((int)code, out description);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the standard status description for a HTTP status code
|
||||
/// specified as an <see langword="int"/>.
|
||||
/// </summary>
|
||||
/// <param name="code">The HTTP status code for which the standard description
|
||||
/// is to be retrieved.</param>
|
||||
/// <param name="description">When this method returns, the standard HTTP status description
|
||||
/// for the specified <paramref name="code"/> if it was found, or <see langword="null"/>
|
||||
/// if it was not found. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if the specified <paramref name="code"/> was found
|
||||
/// in the list of HTTP status codes for which the standard description is known;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
/// <seealso cref="TryGet(HttpStatusCode,out string)"/>
|
||||
/// <seealso cref="Get(int)"/>
|
||||
public static bool TryGet(int code, out string description) => Dictionary.TryGetValue(code, out description);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the standard status description for a <see cref="HttpStatusCode"/>.
|
||||
/// </summary>
|
||||
/// <param name="code">The HTTP status code for which the standard description
|
||||
/// is to be retrieved.</param>
|
||||
/// <returns>The standard HTTP status description for the specified <paramref name="code"/>
|
||||
/// if it was found, or <see langword="null"/> if it was not found.</returns>
|
||||
public static string Get(HttpStatusCode code)
|
||||
{
|
||||
Dictionary.TryGetValue((int)code, out var description);
|
||||
return description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the standard status description for a HTTP status code
|
||||
/// specified as an <see langword="int"/>.
|
||||
/// </summary>
|
||||
/// <param name="code">The HTTP status code for which the standard description
|
||||
/// is to be retrieved.</param>
|
||||
/// <returns>The standard HTTP status description for the specified <paramref name="code"/>
|
||||
/// if it was found, or <see langword="null"/> if it was not found.</returns>
|
||||
public static string Get(int code)
|
||||
{
|
||||
Dictionary.TryGetValue(code, out var description);
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Vendor/EmbedIO-3.5.2/HttpVerbs.cs
vendored
Normal file
48
Vendor/EmbedIO-3.5.2/HttpVerbs.cs
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates the different HTTP Verbs.
|
||||
/// </summary>
|
||||
public enum HttpVerbs
|
||||
{
|
||||
/// <summary>
|
||||
/// Wildcard Method
|
||||
/// </summary>
|
||||
Any,
|
||||
|
||||
/// <summary>
|
||||
/// DELETE Method
|
||||
/// </summary>
|
||||
Delete,
|
||||
|
||||
/// <summary>
|
||||
/// GET Method
|
||||
/// </summary>
|
||||
Get,
|
||||
|
||||
/// <summary>
|
||||
/// HEAD method
|
||||
/// </summary>
|
||||
Head,
|
||||
|
||||
/// <summary>
|
||||
/// OPTIONS method
|
||||
/// </summary>
|
||||
Options,
|
||||
|
||||
/// <summary>
|
||||
/// PATCH method
|
||||
/// </summary>
|
||||
Patch,
|
||||
|
||||
/// <summary>
|
||||
/// POST method
|
||||
/// </summary>
|
||||
Post,
|
||||
|
||||
/// <summary>
|
||||
/// PUT method
|
||||
/// </summary>
|
||||
Put,
|
||||
}
|
||||
}
|
||||
49
Vendor/EmbedIO-3.5.2/ICookieCollection.cs
vendored
Normal file
49
Vendor/EmbedIO-3.5.2/ICookieCollection.cs
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Net;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for Cookie Collection.
|
||||
/// </summary>
|
||||
/// <seealso cref="ICollection" />
|
||||
#pragma warning disable CA1010 // Should implement ICollection<Cookie> - not possible when wrapping System.Net.CookieCollection.
|
||||
public interface ICookieCollection : IEnumerable<Cookie>, ICollection
|
||||
#pragma warning restore CA1010
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Cookie"/> with the specified name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="Cookie"/>.
|
||||
/// </value>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>The cookie matching the specified name.</returns>
|
||||
Cookie? this[string name] { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this <see cref="ICookieCollection"/> contains the specified <see cref="Cookie"/>.
|
||||
/// </summary>
|
||||
/// <param name="cookie">The cookie to find in the <see cref="ICookieCollection"/>.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if this <see cref="ICookieCollection"/> contains the specified <paramref name="cookie"/>;
|
||||
/// otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
bool Contains(Cookie cookie);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the elements of this <see cref="ICookieCollection"/> to a <see cref="Cookie"/> array
|
||||
/// starting at the specified index of the target array.
|
||||
/// </summary>
|
||||
/// <param name="array">The target <see cref="Cookie"/> array to which the <see cref="ICookieCollection"/> will be copied.</param>
|
||||
/// <param name="index">The zero-based index in the target <paramref name="array"/> where copying begins.</param>
|
||||
void CopyTo(Cookie[] array, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified cookie.
|
||||
/// </summary>
|
||||
/// <param name="cookie">The cookie.</param>
|
||||
void Add(Cookie cookie);
|
||||
}
|
||||
}
|
||||
128
Vendor/EmbedIO-3.5.2/IHttpContext.cs
vendored
Normal file
128
Vendor/EmbedIO-3.5.2/IHttpContext.cs
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.Sessions;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the context of a HTTP(s) request being handled by a web server.
|
||||
/// </summary>
|
||||
public interface IHttpContext : IMimeTypeProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a unique identifier for a HTTP context.
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="CancellationToken" /> used to stop processing of this context.
|
||||
/// </summary>
|
||||
CancellationToken CancellationToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server IP address and port number to which the request is directed.
|
||||
/// </summary>
|
||||
IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the client IP address and port number from which the request originated.
|
||||
/// </summary>
|
||||
IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP request.
|
||||
/// </summary>
|
||||
IHttpRequest Request { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the route matched by the requested URL path.
|
||||
/// </summary>
|
||||
RouteMatch Route { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the requested path, relative to the innermost module's base path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This property derives from the path specified in the requested URL, stripped of the
|
||||
/// <see cref="IWebModule.BaseRoute">BaseRoute</see> of the handling module.</para>
|
||||
/// <para>This property is in itself a valid URL path, including an initial
|
||||
/// slash (<c>/</c>) character.</para>
|
||||
/// </remarks>
|
||||
string RequestedPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP response object.
|
||||
/// </summary>
|
||||
IHttpResponse Response { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user.
|
||||
/// </summary>
|
||||
IPrincipal User { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the session proxy associated with this context.
|
||||
/// </summary>
|
||||
ISessionProxy Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether compressed request bodies are supported.
|
||||
/// </summary>
|
||||
/// <seealso cref="WebServerOptionsBase.SupportCompressedRequests"/>
|
||||
bool SupportCompressedRequests { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dictionary of data to pass trough the EmbedIO pipeline.
|
||||
/// </summary>
|
||||
IDictionary<object, object> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the elapsed time, expressed in milliseconds, since the creation of this context.
|
||||
/// </summary>
|
||||
long Age { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets a value indicating whether this <see cref="IHttpContext"/>
|
||||
/// has been completely handled, so that no further processing is required.</para>
|
||||
/// <para>When a HTTP context is created, this property is <see langword="false" />;
|
||||
/// as soon as it is set to <see langword="true" />, the context is not
|
||||
/// passed to any further module's handler for processing.</para>
|
||||
/// <para>Once it becomes <see langword="true" />, this property is guaranteed
|
||||
/// to never become <see langword="false" /> again.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When a module's <see cref="IWebModule.IsFinalHandler">IsFinalHandler</see> property is
|
||||
/// <see langword="true" />, this property is set to <see langword="true" /> after the <see cref="Task" />
|
||||
/// returned by the module's <see cref="IWebModule.HandleRequestAsync">HandleRequestAsync</see> method
|
||||
/// is completed.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="SetHandled" />
|
||||
/// <seealso cref="IWebModule.IsFinalHandler"/>
|
||||
bool IsHandled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Marks this context as handled, so that it will not be
|
||||
/// processed by any further module.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Calling this method from the <see cref="IWebModule.HandleRequestAsync" />
|
||||
/// or <see cref="WebModuleBase.OnRequestAsync" /> of a module whose
|
||||
/// <see cref="IWebModule.IsFinalHandler" /> property is <see langword="true" />
|
||||
/// is redundant and has no effect.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="IsHandled"/>
|
||||
/// <seealso cref="IWebModule.IsFinalHandler"/>
|
||||
void SetHandled();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a callback to be called when processing is finished on a context.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback.</param>
|
||||
void OnClose(Action<IHttpContext> callback);
|
||||
}
|
||||
}
|
||||
20
Vendor/EmbedIO-3.5.2/IHttpContextHandler.cs
vendored
Normal file
20
Vendor/EmbedIO-3.5.2/IHttpContextHandler.cs
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Represents an object that can handle a HTTP context.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
public interface IHttpContextHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Asynchronously handles a HTTP context, generating a suitable response
|
||||
/// for an incoming request.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
Task HandleContextAsync(IHttpContextImpl context);
|
||||
}
|
||||
}
|
||||
88
Vendor/EmbedIO-3.5.2/IHttpContextImpl.cs
vendored
Normal file
88
Vendor/EmbedIO-3.5.2/IHttpContextImpl.cs
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.Sessions;
|
||||
using EmbedIO.Utilities;
|
||||
using EmbedIO.WebSockets;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Represents a HTTP context implementation, i.e. a HTTP context as seen internally by EmbedIO.</para>
|
||||
/// <para>This API mainly supports the EmbedIO infrastructure; it is not intended to be used directly from your code,
|
||||
/// unless to address specific needs in the implementation of EmbedIO plug-ins (e.g. modules).</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="IHttpContext" />
|
||||
public interface IHttpContextImpl : IHttpContext
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a <see cref="CancellationToken" /> used to stop processing of this context.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
new CancellationToken CancellationToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the route matched by the requested URL path.
|
||||
/// </summary>
|
||||
new RouteMatch Route { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the session proxy associated with this context.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// An <see cref="ISessionProxy"/> interface.
|
||||
/// </value>
|
||||
new ISessionProxy Session { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the user.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
new IPrincipal User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a value indicating whether compressed request bodies are supported.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="WebServerOptionsBase.SupportCompressedRequests"/>
|
||||
new bool SupportCompressedRequests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets the MIME type providers.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
MimeTypeProviderStack MimeTypeProviders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Flushes and closes the response stream, then calls any registered close callbacks.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="IHttpContext.OnClose"/>
|
||||
void Close();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Asynchronously handles a WebSockets opening handshake
|
||||
/// and returns a newly-created <seealso cref="IWebSocketContext"/> interface.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="requestedProtocols">The requested WebSocket sub-protocols.</param>
|
||||
/// <param name="acceptedProtocol">The accepted WebSocket sub-protocol,
|
||||
/// or the empty string is no sub-protocol has been agreed upon.</param>
|
||||
/// <param name="receiveBufferSize">Size of the receive buffer.</param>
|
||||
/// <param name="keepAliveInterval">The keep-alive interval.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to stop the server.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="IWebSocketContext"/> interface.
|
||||
/// </returns>
|
||||
Task<IWebSocketContext> AcceptWebSocketAsync(
|
||||
IEnumerable<string> requestedProtocols,
|
||||
string acceptedProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
58
Vendor/EmbedIO-3.5.2/IHttpException.cs
vendored
Normal file
58
Vendor/EmbedIO-3.5.2/IHttpException.cs
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Represents an exception that results in a particular
|
||||
/// HTTP response to be sent to the client.</para>
|
||||
/// <para>This interface is meant to be implemented
|
||||
/// by classes derived from <see cref="Exception" />.</para>
|
||||
/// <para>Either as message or a data object can be attached to
|
||||
/// the exception; which one, if any, is sent to the client
|
||||
/// will depend upon the handler used to send the response.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="HttpExceptionHandlerCallback"/>
|
||||
/// <seealso cref="HttpExceptionHandler"/>
|
||||
public interface IHttpException
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the response status code for a HTTP exception.
|
||||
/// </summary>
|
||||
int StatusCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stack trace of a HTTP exception.
|
||||
/// </summary>
|
||||
string StackTrace { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets a message that can be included in the response triggered
|
||||
/// by a HTTP exception.</para>
|
||||
/// <para>Whether the message is actually sent to the client will depend
|
||||
/// upon the handler used to send the response.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Do not rely on <see cref="Exception.Message"/> to implement
|
||||
/// this property if you want to support <see langword="null"/> messages,
|
||||
/// because a default message will be supplied by the CLR at throw time
|
||||
/// when <see cref="Exception.Message"/> is <see langword="null"/>.</para>
|
||||
/// </remarks>
|
||||
string? Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets an object that can be serialized and included
|
||||
/// in the response triggered by a HTTP exception.</para>
|
||||
/// <para>Whether the object is actually sent to the client will depend
|
||||
/// upon the handler used to send the response.</para>
|
||||
/// </summary>
|
||||
object? DataObject { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets necessary headers, as required by the nature
|
||||
/// of the HTTP exception (e.g. <c>Location</c> for
|
||||
/// <see cref="HttpRedirectException" />).
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context of the response.</param>
|
||||
void PrepareResponse(IHttpContext context);
|
||||
}
|
||||
}
|
||||
72
Vendor/EmbedIO-3.5.2/IHttpListener.cs
vendored
Normal file
72
Vendor/EmbedIO-3.5.2/IHttpListener.cs
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to create a HTTP Listener.
|
||||
/// </summary>
|
||||
public interface IHttpListener : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the listener should ignore write exceptions. By default the flag is set on.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [ignore write exceptions]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
bool IgnoreWriteExceptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the prefixes.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The prefixes.
|
||||
/// </value>
|
||||
List<string> Prefixes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is listening.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is listening; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
bool IsListening { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts this listener.
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Stops this listener.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1716 // Rename method to avoid conflict with (VB) keyword - It is consistent with Microsoft's HttpListener
|
||||
void Stop();
|
||||
#pragma warning restore CA1716
|
||||
|
||||
/// <summary>
|
||||
/// Adds the prefix.
|
||||
/// </summary>
|
||||
/// <param name="urlPrefix">The URL prefix.</param>
|
||||
void AddPrefix(string urlPrefix);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP context asynchronous.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the time delay for the HTTP Context.
|
||||
/// </returns>
|
||||
Task<IHttpContextImpl> GetContextAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
26
Vendor/EmbedIO-3.5.2/IHttpMessage.cs
vendored
Normal file
26
Vendor/EmbedIO-3.5.2/IHttpMessage.cs
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a HTTP request or response.
|
||||
/// </summary>
|
||||
public interface IHttpMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the cookies.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The cookies.
|
||||
/// </value>
|
||||
ICookieCollection Cookies { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the protocol version.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The protocol version.
|
||||
/// </value>
|
||||
Version ProtocolVersion { get; }
|
||||
}
|
||||
}
|
||||
115
Vendor/EmbedIO-3.5.2/IHttpRequest.cs
vendored
Normal file
115
Vendor/EmbedIO-3.5.2/IHttpRequest.cs
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Interface to create a HTTP Request.
|
||||
/// </summary>
|
||||
public interface IHttpRequest : IHttpMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the request headers.
|
||||
/// </summary>
|
||||
NameValueCollection Headers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether [keep alive].
|
||||
/// </summary>
|
||||
bool KeepAlive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw URL.
|
||||
/// </summary>
|
||||
string RawUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query string.
|
||||
/// </summary>
|
||||
NameValueCollection QueryString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP method.
|
||||
/// </summary>
|
||||
string HttpMethod { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="HttpVerbs"/> constant representing the HTTP method of the request.
|
||||
/// </summary>
|
||||
HttpVerbs HttpVerb { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL.
|
||||
/// </summary>
|
||||
Uri Url { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has entity body.
|
||||
/// </summary>
|
||||
bool HasEntityBody { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the input stream.
|
||||
/// </summary>
|
||||
Stream InputStream { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content encoding.
|
||||
/// </summary>
|
||||
Encoding ContentEncoding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remote end point.
|
||||
/// </summary>
|
||||
IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is local.
|
||||
/// </summary>
|
||||
bool IsLocal { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this request has been received over a SSL connection.
|
||||
/// </summary>
|
||||
bool IsSecureConnection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user agent.
|
||||
/// </summary>
|
||||
string UserAgent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is web socket request.
|
||||
/// </summary>
|
||||
bool IsWebSocketRequest { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local end point.
|
||||
/// </summary>
|
||||
IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the content.
|
||||
/// </summary>
|
||||
string? ContentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content length.
|
||||
/// </summary>
|
||||
long ContentLength64 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is authenticated.
|
||||
/// </summary>
|
||||
bool IsAuthenticated { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL referrer.
|
||||
/// </summary>
|
||||
Uri? UrlReferrer { get; }
|
||||
}
|
||||
}
|
||||
69
Vendor/EmbedIO-3.5.2/IHttpResponse.cs
vendored
Normal file
69
Vendor/EmbedIO-3.5.2/IHttpResponse.cs
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Interface to create a HTTP Response.
|
||||
/// </summary>
|
||||
public interface IHttpResponse : IHttpMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the response headers.
|
||||
/// </summary>
|
||||
WebHeaderCollection Headers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status code.
|
||||
/// </summary>
|
||||
int StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content length.
|
||||
/// </summary>
|
||||
long ContentLength64 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the content.
|
||||
/// </summary>
|
||||
string ContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the output stream.
|
||||
/// </summary>
|
||||
Stream OutputStream { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content encoding.
|
||||
/// </summary>
|
||||
Encoding? ContentEncoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [keep alive].
|
||||
/// </summary>
|
||||
bool KeepAlive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the response uses chunked transfer encoding.
|
||||
/// </summary>
|
||||
bool SendChunked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a text description of the HTTP status code.
|
||||
/// </summary>
|
||||
string StatusDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the cookie.
|
||||
/// </summary>
|
||||
/// <param name="cookie">The session cookie.</param>
|
||||
void SetCookie(Cookie cookie);
|
||||
|
||||
/// <summary>
|
||||
/// Closes this instance and dispose the resources.
|
||||
/// </summary>
|
||||
void Close();
|
||||
}
|
||||
}
|
||||
45
Vendor/EmbedIO-3.5.2/IMimeTypeCustomizer.cs
vendored
Normal file
45
Vendor/EmbedIO-3.5.2/IMimeTypeCustomizer.cs
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that can set information about specific MIME types and media ranges,
|
||||
/// to be later retrieved via an <see cref="IMimeTypeProvider"/> interface.
|
||||
/// </summary>
|
||||
/// <seealso cref="IMimeTypeProvider" />
|
||||
public interface IMimeTypeCustomizer : IMimeTypeProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a custom association between a file extension and a MIME type.
|
||||
/// </summary>
|
||||
/// <param name="extension">The file extension to associate to <paramref name="mimeType"/>.</param>
|
||||
/// <param name="mimeType">The MIME type to associate to <paramref name="extension"/>.</param>
|
||||
/// <exception cref="InvalidOperationException">The object implementing <see cref="IMimeTypeCustomizer"/>
|
||||
/// has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="extension"/>is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mimeType"/>is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="extension"/>is the empty string.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mimeType"/>is not a valid MIME type.</para>
|
||||
/// </exception>
|
||||
void AddCustomMimeType(string extension, string mimeType);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether to prefer compression when negotiating content encoding
|
||||
/// for a response with the specified content type, or whose content type is in
|
||||
/// the specified media range.
|
||||
/// </summary>
|
||||
/// <param name="mimeType">The MIME type or media range.</param>
|
||||
/// <param name="preferCompression"><see langword="true"/> to prefer compression;
|
||||
/// otherwise, <see langword="false"/>.</param>
|
||||
/// <exception cref="InvalidOperationException">The object implementing <see cref="IMimeTypeCustomizer"/>
|
||||
/// has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="mimeType"/>is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="mimeType"/>is not a valid MIME type or media range.</exception>
|
||||
void PreferCompression(string mimeType, bool preferCompression);
|
||||
}
|
||||
}
|
||||
31
Vendor/EmbedIO-3.5.2/IMimeTypeProvider.cs
vendored
Normal file
31
Vendor/EmbedIO-3.5.2/IMimeTypeProvider.cs
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that contains information on specific MIME types and media ranges.
|
||||
/// </summary>
|
||||
public interface IMimeTypeProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the MIME type associated to a file extension.
|
||||
/// </summary>
|
||||
/// <param name="extension">The file extension for which a corresponding MIME type is wanted.</param>
|
||||
/// <returns>The MIME type corresponding to <paramref name="extension"/>, if one is found;
|
||||
/// otherwise, <see langword="null"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="extension"/>is <see langword="null"/>.</exception>
|
||||
string GetMimeType(string extension);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to determine whether compression should be preferred
|
||||
/// when negotiating content encoding for a response with the specified content type.
|
||||
/// </summary>
|
||||
/// <param name="mimeType">The MIME type to check.</param>
|
||||
/// <param name="preferCompression">When this method returns <see langword="true"/>,
|
||||
/// a value indicating whether compression should be preferred.
|
||||
/// This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if a value is found for <paramref name="mimeType"/>;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
bool TryDetermineCompression(string mimeType, out bool preferCompression);
|
||||
}
|
||||
}
|
||||
81
Vendor/EmbedIO-3.5.2/IWebModule.cs
vendored
Normal file
81
Vendor/EmbedIO-3.5.2/IWebModule.cs
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Routing;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a module.
|
||||
/// </summary>
|
||||
public interface IWebModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the base route of a module.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The base route.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// <para>A base route is either "/" (the root path),
|
||||
/// or a prefix starting and ending with a '/' character.</para>
|
||||
/// </remarks>
|
||||
string BaseRoute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether processing of a request should stop
|
||||
/// after a module has handled it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If this property is <see langword="true" />, a HTTP context's
|
||||
/// <see cref="IHttpContext.SetHandled" /> method will be automatically called
|
||||
/// immediately after after the <see cref="Task" /> returned by
|
||||
/// <see cref="HandleRequestAsync" /> is completed. This will prevent
|
||||
/// the context from being passed further along to other modules.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="IHttpContext.IsHandled" />
|
||||
/// <seealso cref="IHttpContext.SetHandled" />
|
||||
bool IsFinalHandler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a callback that is called every time an unhandled exception
|
||||
/// occurs during the processing of a request.</para>
|
||||
/// <para>If this property is <see langword="null"/> (the default),
|
||||
/// the exception will be handled by the web server, or by the containing
|
||||
/// <see cref="ModuleGroup"/>.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="ExceptionHandler"/>
|
||||
ExceptionHandlerCallback? OnUnhandledException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a callback that is called every time a HTTP exception
|
||||
/// is thrown during the processing of a request.</para>
|
||||
/// <para>If this property is <see langword="null"/> (the default),
|
||||
/// the exception will be handled by the web server, or by the containing
|
||||
/// <see cref="ModuleGroup"/>.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="HttpExceptionHandler"/>
|
||||
HttpExceptionHandlerCallback? OnHttpException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signals a module that the web server is starting.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to stop the web server.</param>
|
||||
void Start(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Matches the specified URL path against a module's <see cref="BaseRoute"/>,
|
||||
/// extracting values for the route's parameters and a sub-path.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path to match.</param>
|
||||
/// <returns>If the match is successful, a <see cref="RouteMatch"/> object;
|
||||
/// otherwise, <see langword="null"/>.</returns>
|
||||
RouteMatch MatchUrlPath(string urlPath);
|
||||
|
||||
/// <summary>
|
||||
/// Handles a request from a client.
|
||||
/// </summary>
|
||||
/// <param name="context">The context of the request being handled.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
Task HandleRequestAsync(IHttpContext context);
|
||||
}
|
||||
}
|
||||
19
Vendor/EmbedIO-3.5.2/IWebModuleContainer.cs
vendored
Normal file
19
Vendor/EmbedIO-3.5.2/IWebModuleContainer.cs
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that contains a collection of <see cref="IWebModule"/> interfaces.
|
||||
/// </summary>
|
||||
public interface IWebModuleContainer : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the modules.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The modules.
|
||||
/// </value>
|
||||
IComponentCollection<IWebModule> Modules { get; }
|
||||
}
|
||||
}
|
||||
68
Vendor/EmbedIO-3.5.2/IWebServer.cs
vendored
Normal file
68
Vendor/EmbedIO-3.5.2/IWebServer.cs
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Sessions;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Represents a web server.</para>
|
||||
/// <para>The basic usage of a web server is as follows:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>add modules to the <see cref="IWebModuleContainer.Modules">Modules</see> collection;</description></item>
|
||||
/// <item><description>set a <see cref="SessionManager"/> if needed;</description></item>
|
||||
/// <item><description>call <see cref="RunAsync"/> to respond to incoming requests.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public interface IWebServer : IWebModuleContainer, IMimeTypeCustomizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="State"/> property changes.
|
||||
/// </summary>
|
||||
event WebServerStateChangedEventHandler StateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a callback that is called every time an unhandled exception
|
||||
/// occurs during the processing of a request.</para>
|
||||
/// <para>This property can never be <see langword="null"/>.
|
||||
/// If it is still </para>
|
||||
/// </summary>
|
||||
/// <seealso cref="ExceptionHandler"/>
|
||||
ExceptionHandlerCallback OnUnhandledException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a callback that is called every time a HTTP exception
|
||||
/// is thrown during the processing of a request.</para>
|
||||
/// <para>This property can never be <see langword="null"/>.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="HttpExceptionHandler"/>
|
||||
HttpExceptionHandlerCallback OnHttpException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the registered session ID manager, if any.</para>
|
||||
/// <para>A session ID manager is an implementation of <see cref="ISessionManager"/>.</para>
|
||||
/// <para>Note that this property can only be set before starting the web server.</para>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The session manager, or <see langword="null"/> if no session manager is present.
|
||||
/// </value>
|
||||
/// <exception cref="InvalidOperationException">This property is being set and the web server has already been started.</exception>
|
||||
ISessionManager? SessionManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the web server.
|
||||
/// </summary>
|
||||
/// <value>The state.</value>
|
||||
/// <seealso cref="WebServerState"/>
|
||||
WebServerState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts the listener and the registered modules.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token; when cancelled, the server cancels all pending requests and stops.</param>
|
||||
/// <returns>
|
||||
/// Returns the task that the HTTP listener is running inside of, so that it can be waited upon after it's been canceled.
|
||||
/// </returns>
|
||||
Task RunAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
87
Vendor/EmbedIO-3.5.2/Internal/BufferingResponseStream.cs
vendored
Normal file
87
Vendor/EmbedIO-3.5.2/Internal/BufferingResponseStream.cs
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
// Wraps a response's output stream, buffering all data
|
||||
// in a MemoryStream.
|
||||
// When disposed, sets the response's ContentLength and copies all data
|
||||
// to the output stream.
|
||||
internal class BufferingResponseStream : Stream
|
||||
{
|
||||
private readonly IHttpResponse _response;
|
||||
private readonly MemoryStream _buffer;
|
||||
|
||||
public BufferingResponseStream(IHttpResponse response)
|
||||
{
|
||||
_response = response;
|
||||
_buffer = new MemoryStream();
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => _buffer.Length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _buffer.Position;
|
||||
set => throw SeekingNotSupported();
|
||||
}
|
||||
|
||||
public override void Flush() => _buffer.Flush();
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken) => _buffer.FlushAsync(cancellationToken);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) => throw ReadingNotSupported();
|
||||
|
||||
public override int ReadByte() => throw ReadingNotSupported();
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
=> throw ReadingNotSupported();
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult) => throw ReadingNotSupported();
|
||||
|
||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw ReadingNotSupported();
|
||||
|
||||
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
=> throw ReadingNotSupported();
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw SeekingNotSupported();
|
||||
|
||||
public override void SetLength(long value) => throw SeekingNotSupported();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) => _buffer.Write(buffer, offset, count);
|
||||
|
||||
public override void WriteByte(byte value) => _buffer.WriteByte(value);
|
||||
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
=> _buffer.BeginWrite(buffer, offset, count, callback, state);
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult) => _buffer.EndWrite(asyncResult);
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
=> _buffer.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_response.ContentLength64 = _buffer.Length;
|
||||
_buffer.Position = 0;
|
||||
_buffer.CopyTo(_response.OutputStream);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_buffer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static Exception ReadingNotSupported() => new NotSupportedException("This stream does not support reading.");
|
||||
|
||||
private static Exception SeekingNotSupported() => new NotSupportedException("This stream does not support seeking.");
|
||||
}
|
||||
}
|
||||
121
Vendor/EmbedIO-3.5.2/Internal/CompressionStream.cs
vendored
Normal file
121
Vendor/EmbedIO-3.5.2/Internal/CompressionStream.cs
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal class CompressionStream : Stream
|
||||
{
|
||||
private readonly Stream _target;
|
||||
private readonly bool _leaveOpen;
|
||||
|
||||
public CompressionStream(Stream target, CompressionMethod compressionMethod)
|
||||
{
|
||||
switch (compressionMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
_target = new DeflateStream(target, CompressionMode.Compress, true);
|
||||
_leaveOpen = false;
|
||||
break;
|
||||
case CompressionMethod.Gzip:
|
||||
_target = new GZipStream(target, CompressionMode.Compress, true);
|
||||
_leaveOpen = false;
|
||||
break;
|
||||
default:
|
||||
_target = target;
|
||||
_leaveOpen = true;
|
||||
break;
|
||||
}
|
||||
|
||||
UncompressedLength = 0;
|
||||
}
|
||||
|
||||
public long UncompressedLength { get; private set; }
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw SeekingNotSupported();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw SeekingNotSupported();
|
||||
set => throw SeekingNotSupported();
|
||||
}
|
||||
|
||||
public override void Flush() => _target.Flush();
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken) => _target.FlushAsync(cancellationToken);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) => throw ReadingNotSupported();
|
||||
|
||||
public override int ReadByte() => throw ReadingNotSupported();
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
=> throw ReadingNotSupported();
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult) => throw ReadingNotSupported();
|
||||
|
||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw ReadingNotSupported();
|
||||
|
||||
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
=> throw ReadingNotSupported();
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw SeekingNotSupported();
|
||||
|
||||
public override void SetLength(long value) => throw SeekingNotSupported();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_target.Write(buffer, offset, count);
|
||||
UncompressedLength += count;
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
_target.WriteByte(value);
|
||||
UncompressedLength++;
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
=> _target.BeginWrite(
|
||||
buffer,
|
||||
offset,
|
||||
count,
|
||||
ar => {
|
||||
UncompressedLength += count;
|
||||
callback(ar);
|
||||
},
|
||||
state);
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult)
|
||||
{
|
||||
_target.EndWrite(asyncResult);
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
await _target.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
UncompressedLength += count;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && !_leaveOpen)
|
||||
{
|
||||
_target.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private static Exception ReadingNotSupported() => new NotSupportedException("This stream does not support reading.");
|
||||
|
||||
private static Exception SeekingNotSupported() => new NotSupportedException("This stream does not support seeking.");
|
||||
}
|
||||
}
|
||||
82
Vendor/EmbedIO-3.5.2/Internal/CompressionUtility.cs
vendored
Normal file
82
Vendor/EmbedIO-3.5.2/Internal/CompressionUtility.cs
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal static class CompressionUtility
|
||||
{
|
||||
public static byte[]? ConvertCompression(byte[] source, CompressionMethod sourceMethod, CompressionMethod targetMethod)
|
||||
{
|
||||
if (source == null)
|
||||
return null;
|
||||
|
||||
if (sourceMethod == targetMethod)
|
||||
return source;
|
||||
|
||||
switch (sourceMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
using (var sourceStream = new MemoryStream(source, false))
|
||||
{
|
||||
using var decompressionStream = new DeflateStream(sourceStream, CompressionMode.Decompress, true);
|
||||
using var targetStream = new MemoryStream();
|
||||
if (targetMethod == CompressionMethod.Gzip)
|
||||
{
|
||||
using var compressionStream = new GZipStream(targetStream, CompressionMode.Compress, true);
|
||||
decompressionStream.CopyTo(compressionStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
decompressionStream.CopyTo(targetStream);
|
||||
}
|
||||
|
||||
return targetStream.ToArray();
|
||||
}
|
||||
|
||||
case CompressionMethod.Gzip:
|
||||
using (var sourceStream = new MemoryStream(source, false))
|
||||
{
|
||||
using var decompressionStream = new GZipStream(sourceStream, CompressionMode.Decompress, true);
|
||||
using var targetStream = new MemoryStream();
|
||||
if (targetMethod == CompressionMethod.Deflate)
|
||||
{
|
||||
using var compressionStream = new DeflateStream(targetStream, CompressionMode.Compress, true);
|
||||
decompressionStream.CopyToAsync(compressionStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
decompressionStream.CopyTo(targetStream);
|
||||
}
|
||||
|
||||
return targetStream.ToArray();
|
||||
}
|
||||
|
||||
default:
|
||||
using (var sourceStream = new MemoryStream(source, false))
|
||||
{
|
||||
using var targetStream = new MemoryStream();
|
||||
switch (targetMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
using (var compressionStream = new DeflateStream(targetStream, CompressionMode.Compress, true))
|
||||
sourceStream.CopyTo(compressionStream);
|
||||
|
||||
break;
|
||||
|
||||
case CompressionMethod.Gzip:
|
||||
using (var compressionStream = new GZipStream(targetStream, CompressionMode.Compress, true))
|
||||
sourceStream.CopyTo(compressionStream);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
// Just in case. Consider all other values as None.
|
||||
return source;
|
||||
}
|
||||
|
||||
return targetStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Vendor/EmbedIO-3.5.2/Internal/DummyWebModuleContainer.cs
vendored
Normal file
27
Vendor/EmbedIO-3.5.2/Internal/DummyWebModuleContainer.cs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal sealed class DummyWebModuleContainer : IWebModuleContainer
|
||||
{
|
||||
public static readonly IWebModuleContainer Instance = new DummyWebModuleContainer();
|
||||
|
||||
private DummyWebModuleContainer()
|
||||
{
|
||||
}
|
||||
|
||||
public IComponentCollection<IWebModule> Modules => throw UnexpectedCall();
|
||||
|
||||
public ConcurrentDictionary<object, object> SharedItems => throw UnexpectedCall();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private InternalErrorException UnexpectedCall([CallerMemberName] string member = "")
|
||||
=> SelfCheck.Failure($"Unexpected call to {nameof(DummyWebModuleContainer)}.{member}.");
|
||||
}
|
||||
}
|
||||
9
Vendor/EmbedIO-3.5.2/Internal/LockableNameValueCollection.cs
vendored
Normal file
9
Vendor/EmbedIO-3.5.2/Internal/LockableNameValueCollection.cs
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal sealed class LockableNameValueCollection : NameValueCollection
|
||||
{
|
||||
public void MakeReadOnly() => IsReadOnly = true;
|
||||
}
|
||||
}
|
||||
63
Vendor/EmbedIO-3.5.2/Internal/MimeTypeCustomizer.cs
vendored
Normal file
63
Vendor/EmbedIO-3.5.2/Internal/MimeTypeCustomizer.cs
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan.Configuration;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal sealed class MimeTypeCustomizer : ConfiguredObject, IMimeTypeCustomizer
|
||||
{
|
||||
private readonly Dictionary<string, string> _customMimeTypes = new Dictionary<string, string>();
|
||||
private readonly Dictionary<(string, string), bool> _data = new Dictionary<(string, string), bool>();
|
||||
|
||||
private bool? _defaultPreferCompression;
|
||||
|
||||
public string GetMimeType(string extension)
|
||||
{
|
||||
_customMimeTypes.TryGetValue(Validate.NotNull(nameof(extension), extension), out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
{
|
||||
var (type, subtype) = MimeType.UnsafeSplit(
|
||||
Validate.MimeType(nameof(mimeType), mimeType, false));
|
||||
|
||||
if (_data.TryGetValue((type, subtype), out preferCompression))
|
||||
return true;
|
||||
|
||||
if (_data.TryGetValue((type, "*"), out preferCompression))
|
||||
return true;
|
||||
|
||||
if (!_defaultPreferCompression.HasValue)
|
||||
return false;
|
||||
|
||||
preferCompression = _defaultPreferCompression.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddCustomMimeType(string extension, string mimeType)
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_customMimeTypes[Validate.NotNullOrEmpty(nameof(extension), extension)]
|
||||
= Validate.MimeType(nameof(mimeType), mimeType, false);
|
||||
}
|
||||
|
||||
public void PreferCompression(string mimeType, bool preferCompression)
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
var (type, subtype) = MimeType.UnsafeSplit(
|
||||
Validate.MimeType(nameof(mimeType), mimeType, true));
|
||||
|
||||
if (type == "*")
|
||||
{
|
||||
_defaultPreferCompression = preferCompression;
|
||||
}
|
||||
else
|
||||
{
|
||||
_data[(type, subtype)] = preferCompression;
|
||||
}
|
||||
}
|
||||
|
||||
public void Lock() => LockConfiguration();
|
||||
}
|
||||
}
|
||||
15
Vendor/EmbedIO-3.5.2/Internal/RequestHandlerPassThroughException.cs
vendored
Normal file
15
Vendor/EmbedIO-3.5.2/Internal/RequestHandlerPassThroughException.cs
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
// This exception is only created and handled internally,
|
||||
// so it doesn't need all the standard the bells and whistles.
|
||||
#pragma warning disable CA1032 // Add standard exception constructors
|
||||
#pragma warning disable CA1064 // Exceptions should be public
|
||||
|
||||
internal class RequestHandlerPassThroughException : Exception
|
||||
{
|
||||
}
|
||||
#pragma warning restore CA1032
|
||||
#pragma warning restore CA1064
|
||||
}
|
||||
27
Vendor/EmbedIO-3.5.2/Internal/TimeKeeper.cs
vendored
Normal file
27
Vendor/EmbedIO-3.5.2/Internal/TimeKeeper.cs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a wrapper around Stopwatch.
|
||||
/// </summary>
|
||||
public sealed class TimeKeeper
|
||||
{
|
||||
private static readonly Stopwatch Stopwatch = Stopwatch.StartNew();
|
||||
|
||||
private readonly long _start;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimeKeeper"/> class.
|
||||
/// </summary>
|
||||
public TimeKeeper()
|
||||
{
|
||||
_start = Stopwatch.ElapsedMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the elapsed time since the class was initialized.
|
||||
/// </summary>
|
||||
public long ElapsedTime => Stopwatch.ElapsedMilliseconds - _start;
|
||||
}
|
||||
}
|
||||
47
Vendor/EmbedIO-3.5.2/Internal/UriUtility.cs
vendored
Normal file
47
Vendor/EmbedIO-3.5.2/Internal/UriUtility.cs
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal static class UriUtility
|
||||
{
|
||||
public static Uri StringToUri(string str)
|
||||
{
|
||||
_ = Uri.TryCreate(str, CanBeAbsoluteUrl(str) ? UriKind.Absolute : UriKind.Relative, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Uri? StringToAbsoluteUri(string str)
|
||||
{
|
||||
if (!CanBeAbsoluteUrl(str))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_ = Uri.TryCreate(str, UriKind.Absolute, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns true if string starts with "http:", "https:", "ws:", or "wss:"
|
||||
private static bool CanBeAbsoluteUrl(string str)
|
||||
=> !string.IsNullOrEmpty(str)
|
||||
&& str[0] switch {
|
||||
'h' => str.Length >= 5
|
||||
&& str[1] == 't'
|
||||
&& str[2] == 't'
|
||||
&& str[3] == 'p'
|
||||
&& str[4] switch {
|
||||
':' => true,
|
||||
's' => str.Length >= 6 && str[5] == ':',
|
||||
_ => false
|
||||
},
|
||||
'w' => str.Length >= 3
|
||||
&& str[1] == 's'
|
||||
&& str[2] switch {
|
||||
':' => true,
|
||||
's' => str.Length >= 4 && str[3] == ':',
|
||||
_ => false
|
||||
},
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
46
Vendor/EmbedIO-3.5.2/Internal/WebModuleCollection.cs
vendored
Normal file
46
Vendor/EmbedIO-3.5.2/Internal/WebModuleCollection.cs
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal sealed class WebModuleCollection : DisposableComponentCollection<IWebModule>
|
||||
{
|
||||
private readonly string _logSource;
|
||||
|
||||
internal WebModuleCollection(string logSource)
|
||||
{
|
||||
_logSource = logSource;
|
||||
}
|
||||
|
||||
internal void StartAll(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var (name, module) in WithSafeNames)
|
||||
{
|
||||
$"Starting module {name}...".Debug(_logSource);
|
||||
module.Start(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task DispatchRequestAsync(IHttpContext context)
|
||||
{
|
||||
if (context.IsHandled)
|
||||
return;
|
||||
|
||||
var requestedPath = context.RequestedPath;
|
||||
foreach (var (name, module) in WithSafeNames)
|
||||
{
|
||||
var routeMatch = module.MatchUrlPath(requestedPath);
|
||||
if (routeMatch == null)
|
||||
continue;
|
||||
|
||||
$"[{context.Id}] Processing with {name}.".Debug(_logSource);
|
||||
context.GetImplementation().Route = routeMatch;
|
||||
await module.HandleRequestAsync(context).ConfigureAwait(false);
|
||||
if (context.IsHandled)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
648
Vendor/EmbedIO-3.5.2/MimeType.Associations.cs
vendored
Normal file
648
Vendor/EmbedIO-3.5.2/MimeType.Associations.cs
vendored
Normal file
@@ -0,0 +1,648 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class MimeType
|
||||
{
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// IMPORTANT NOTE TO CONTRIBUTORS
|
||||
// ==============================
|
||||
//
|
||||
// When you update the MIME type list, remember to:
|
||||
//
|
||||
// * update the date in XML docs below;
|
||||
//
|
||||
// * check the LICENSE file to see if copyright year and/or license conditions have changed;
|
||||
//
|
||||
// * if the URL for the LICENSE file has changed, update EmbedIO's LICENSE file too.
|
||||
//
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// <para>Associates file extensions to MIME types.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The list of MIME types has been copied from
|
||||
/// <see href="https://github.com/samuelneff/MimeTypeMap/blob/master/src/MimeTypes/MimeTypeMap.cs">Samuel Neff's MIME Type Map</see>
|
||||
/// on April 26th, 2019.</para>
|
||||
/// <para>Copyright (c) 2014 Samuel Neff. Redistributed under <see href="https://github.com/samuelneff/MimeTypeMap/blob/master/LICENSE">MIT license</see>.</para>
|
||||
/// </remarks>
|
||||
public static IReadOnlyDictionary<string, string> Associations { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{".323", "text/h323"},
|
||||
{".3g2", "video/3gpp2"},
|
||||
{".3gp", "video/3gpp"},
|
||||
{".3gp2", "video/3gpp2"},
|
||||
{".3gpp", "video/3gpp"},
|
||||
{".7z", "application/x-7z-compressed"},
|
||||
{".aa", "audio/audible"},
|
||||
{".AAC", "audio/aac"},
|
||||
{".aaf", "application/octet-stream"},
|
||||
{".aax", "audio/vnd.audible.aax"},
|
||||
{".ac3", "audio/ac3"},
|
||||
{".aca", "application/octet-stream"},
|
||||
{".accda", "application/msaccess.addin"},
|
||||
{".accdb", "application/msaccess"},
|
||||
{".accdc", "application/msaccess.cab"},
|
||||
{".accde", "application/msaccess"},
|
||||
{".accdr", "application/msaccess.runtime"},
|
||||
{".accdt", "application/msaccess"},
|
||||
{".accdw", "application/msaccess.webapplication"},
|
||||
{".accft", "application/msaccess.ftemplate"},
|
||||
{".acx", "application/internet-property-stream"},
|
||||
{".AddIn", "text/xml"},
|
||||
{".ade", "application/msaccess"},
|
||||
{".adobebridge", "application/x-bridge-url"},
|
||||
{".adp", "application/msaccess"},
|
||||
{".ADT", "audio/vnd.dlna.adts"},
|
||||
{".ADTS", "audio/aac"},
|
||||
{".afm", "application/octet-stream"},
|
||||
{".ai", "application/postscript"},
|
||||
{".aif", "audio/aiff"},
|
||||
{".aifc", "audio/aiff"},
|
||||
{".aiff", "audio/aiff"},
|
||||
{".air", "application/vnd.adobe.air-application-installer-package+zip"},
|
||||
{".amc", "application/mpeg"},
|
||||
{".anx", "application/annodex"},
|
||||
{".apk", "application/vnd.android.package-archive" },
|
||||
{".application", "application/x-ms-application"},
|
||||
{".art", "image/x-jg"},
|
||||
{".asa", "application/xml"},
|
||||
{".asax", "application/xml"},
|
||||
{".ascx", "application/xml"},
|
||||
{".asd", "application/octet-stream"},
|
||||
{".asf", "video/x-ms-asf"},
|
||||
{".ashx", "application/xml"},
|
||||
{".asi", "application/octet-stream"},
|
||||
{".asm", "text/plain"},
|
||||
{".asmx", "application/xml"},
|
||||
{".aspx", "application/xml"},
|
||||
{".asr", "video/x-ms-asf"},
|
||||
{".asx", "video/x-ms-asf"},
|
||||
{".atom", "application/atom+xml"},
|
||||
{".au", "audio/basic"},
|
||||
{".avi", "video/x-msvideo"},
|
||||
{".axa", "audio/annodex"},
|
||||
{".axs", "application/olescript"},
|
||||
{".axv", "video/annodex"},
|
||||
{".bas", "text/plain"},
|
||||
{".bcpio", "application/x-bcpio"},
|
||||
{".bin", "application/octet-stream"},
|
||||
{".bmp", "image/bmp"},
|
||||
{".c", "text/plain"},
|
||||
{".cab", "application/octet-stream"},
|
||||
{".caf", "audio/x-caf"},
|
||||
{".calx", "application/vnd.ms-office.calx"},
|
||||
{".cat", "application/vnd.ms-pki.seccat"},
|
||||
{".cc", "text/plain"},
|
||||
{".cd", "text/plain"},
|
||||
{".cdda", "audio/aiff"},
|
||||
{".cdf", "application/x-cdf"},
|
||||
{".cer", "application/x-x509-ca-cert"},
|
||||
{".cfg", "text/plain"},
|
||||
{".chm", "application/octet-stream"},
|
||||
{".class", "application/x-java-applet"},
|
||||
{".clp", "application/x-msclip"},
|
||||
{".cmd", "text/plain"},
|
||||
{".cmx", "image/x-cmx"},
|
||||
{".cnf", "text/plain"},
|
||||
{".cod", "image/cis-cod"},
|
||||
{".config", "application/xml"},
|
||||
{".contact", "text/x-ms-contact"},
|
||||
{".coverage", "application/xml"},
|
||||
{".cpio", "application/x-cpio"},
|
||||
{".cpp", "text/plain"},
|
||||
{".crd", "application/x-mscardfile"},
|
||||
{".crl", "application/pkix-crl"},
|
||||
{".crt", "application/x-x509-ca-cert"},
|
||||
{".cs", "text/plain"},
|
||||
{".csdproj", "text/plain"},
|
||||
{".csh", "application/x-csh"},
|
||||
{".csproj", "text/plain"},
|
||||
{".css", "text/css"},
|
||||
{".csv", "text/csv"},
|
||||
{".cur", "application/octet-stream"},
|
||||
{".cxx", "text/plain"},
|
||||
{".dat", "application/octet-stream"},
|
||||
{".datasource", "application/xml"},
|
||||
{".dbproj", "text/plain"},
|
||||
{".dcr", "application/x-director"},
|
||||
{".def", "text/plain"},
|
||||
{".deploy", "application/octet-stream"},
|
||||
{".der", "application/x-x509-ca-cert"},
|
||||
{".dgml", "application/xml"},
|
||||
{".dib", "image/bmp"},
|
||||
{".dif", "video/x-dv"},
|
||||
{".dir", "application/x-director"},
|
||||
{".disco", "text/xml"},
|
||||
{".divx", "video/divx"},
|
||||
{".dll", "application/x-msdownload"},
|
||||
{".dll.config", "text/xml"},
|
||||
{".dlm", "text/dlm"},
|
||||
{".doc", "application/msword"},
|
||||
{".docm", "application/vnd.ms-word.document.macroEnabled.12"},
|
||||
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
|
||||
{".dot", "application/msword"},
|
||||
{".dotm", "application/vnd.ms-word.template.macroEnabled.12"},
|
||||
{".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
|
||||
{".dsp", "application/octet-stream"},
|
||||
{".dsw", "text/plain"},
|
||||
{".dtd", "text/xml"},
|
||||
{".dtsConfig", "text/xml"},
|
||||
{".dv", "video/x-dv"},
|
||||
{".dvi", "application/x-dvi"},
|
||||
{".dwf", "drawing/x-dwf"},
|
||||
{".dwg", "application/acad"},
|
||||
{".dwp", "application/octet-stream"},
|
||||
{".dxf", "application/x-dxf" },
|
||||
{".dxr", "application/x-director"},
|
||||
{".eml", "message/rfc822"},
|
||||
{".emz", "application/octet-stream"},
|
||||
{".eot", "application/vnd.ms-fontobject"},
|
||||
{".eps", "application/postscript"},
|
||||
{".es", "application/ecmascript"},
|
||||
{".etl", "application/etl"},
|
||||
{".etx", "text/x-setext"},
|
||||
{".evy", "application/envoy"},
|
||||
{".exe", "application/octet-stream"},
|
||||
{".exe.config", "text/xml"},
|
||||
{".fdf", "application/vnd.fdf"},
|
||||
{".fif", "application/fractals"},
|
||||
{".filters", "application/xml"},
|
||||
{".fla", "application/octet-stream"},
|
||||
{".flac", "audio/flac"},
|
||||
{".flr", "x-world/x-vrml"},
|
||||
{".flv", "video/x-flv"},
|
||||
{".fsscript", "application/fsharp-script"},
|
||||
{".fsx", "application/fsharp-script"},
|
||||
{".generictest", "application/xml"},
|
||||
{".gif", "image/gif"},
|
||||
{".gpx", "application/gpx+xml"},
|
||||
{".group", "text/x-ms-group"},
|
||||
{".gsm", "audio/x-gsm"},
|
||||
{".gtar", "application/x-gtar"},
|
||||
{".gz", "application/x-gzip"},
|
||||
{".h", "text/plain"},
|
||||
{".hdf", "application/x-hdf"},
|
||||
{".hdml", "text/x-hdml"},
|
||||
{".hhc", "application/x-oleobject"},
|
||||
{".hhk", "application/octet-stream"},
|
||||
{".hhp", "application/octet-stream"},
|
||||
{".hlp", "application/winhlp"},
|
||||
{".hpp", "text/plain"},
|
||||
{".hqx", "application/mac-binhex40"},
|
||||
{".hta", "application/hta"},
|
||||
{".htc", "text/x-component"},
|
||||
{".htm", "text/html"},
|
||||
{".html", "text/html"},
|
||||
{".htt", "text/webviewhtml"},
|
||||
{".hxa", "application/xml"},
|
||||
{".hxc", "application/xml"},
|
||||
{".hxd", "application/octet-stream"},
|
||||
{".hxe", "application/xml"},
|
||||
{".hxf", "application/xml"},
|
||||
{".hxh", "application/octet-stream"},
|
||||
{".hxi", "application/octet-stream"},
|
||||
{".hxk", "application/xml"},
|
||||
{".hxq", "application/octet-stream"},
|
||||
{".hxr", "application/octet-stream"},
|
||||
{".hxs", "application/octet-stream"},
|
||||
{".hxt", "text/html"},
|
||||
{".hxv", "application/xml"},
|
||||
{".hxw", "application/octet-stream"},
|
||||
{".hxx", "text/plain"},
|
||||
{".i", "text/plain"},
|
||||
{".ico", "image/x-icon"},
|
||||
{".ics", "application/octet-stream"},
|
||||
{".idl", "text/plain"},
|
||||
{".ief", "image/ief"},
|
||||
{".iii", "application/x-iphone"},
|
||||
{".inc", "text/plain"},
|
||||
{".inf", "application/octet-stream"},
|
||||
{".ini", "text/plain"},
|
||||
{".inl", "text/plain"},
|
||||
{".ins", "application/x-internet-signup"},
|
||||
{".ipa", "application/x-itunes-ipa"},
|
||||
{".ipg", "application/x-itunes-ipg"},
|
||||
{".ipproj", "text/plain"},
|
||||
{".ipsw", "application/x-itunes-ipsw"},
|
||||
{".iqy", "text/x-ms-iqy"},
|
||||
{".isp", "application/x-internet-signup"},
|
||||
{".ite", "application/x-itunes-ite"},
|
||||
{".itlp", "application/x-itunes-itlp"},
|
||||
{".itms", "application/x-itunes-itms"},
|
||||
{".itpc", "application/x-itunes-itpc"},
|
||||
{".IVF", "video/x-ivf"},
|
||||
{".jar", "application/java-archive"},
|
||||
{".java", "application/octet-stream"},
|
||||
{".jck", "application/liquidmotion"},
|
||||
{".jcz", "application/liquidmotion"},
|
||||
{".jfif", "image/pjpeg"},
|
||||
{".jnlp", "application/x-java-jnlp-file"},
|
||||
{".jpb", "application/octet-stream"},
|
||||
{".jpe", "image/jpeg"},
|
||||
{".jpeg", "image/jpeg"},
|
||||
{".jpg", "image/jpeg"},
|
||||
{".js", "application/javascript"},
|
||||
{".json", "application/json"},
|
||||
{".jsx", "text/jscript"},
|
||||
{".jsxbin", "text/plain"},
|
||||
{".latex", "application/x-latex"},
|
||||
{".library-ms", "application/windows-library+xml"},
|
||||
{".lit", "application/x-ms-reader"},
|
||||
{".loadtest", "application/xml"},
|
||||
{".lpk", "application/octet-stream"},
|
||||
{".lsf", "video/x-la-asf"},
|
||||
{".lst", "text/plain"},
|
||||
{".lsx", "video/x-la-asf"},
|
||||
{".lzh", "application/octet-stream"},
|
||||
{".m13", "application/x-msmediaview"},
|
||||
{".m14", "application/x-msmediaview"},
|
||||
{".m1v", "video/mpeg"},
|
||||
{".m2t", "video/vnd.dlna.mpeg-tts"},
|
||||
{".m2ts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".m2v", "video/mpeg"},
|
||||
{".m3u", "audio/x-mpegurl"},
|
||||
{".m3u8", "audio/x-mpegurl"},
|
||||
{".m4a", "audio/m4a"},
|
||||
{".m4b", "audio/m4b"},
|
||||
{".m4p", "audio/m4p"},
|
||||
{".m4r", "audio/x-m4r"},
|
||||
{".m4v", "video/x-m4v"},
|
||||
{".mac", "image/x-macpaint"},
|
||||
{".mak", "text/plain"},
|
||||
{".man", "application/x-troff-man"},
|
||||
{".manifest", "application/x-ms-manifest"},
|
||||
{".map", "text/plain"},
|
||||
{".master", "application/xml"},
|
||||
{".mbox", "application/mbox"},
|
||||
{".mda", "application/msaccess"},
|
||||
{".mdb", "application/x-msaccess"},
|
||||
{".mde", "application/msaccess"},
|
||||
{".mdp", "application/octet-stream"},
|
||||
{".me", "application/x-troff-me"},
|
||||
{".mfp", "application/x-shockwave-flash"},
|
||||
{".mht", "message/rfc822"},
|
||||
{".mhtml", "message/rfc822"},
|
||||
{".mid", "audio/mid"},
|
||||
{".midi", "audio/mid"},
|
||||
{".mix", "application/octet-stream"},
|
||||
{".mk", "text/plain"},
|
||||
{".mk3d", "video/x-matroska-3d"},
|
||||
{".mka", "audio/x-matroska"},
|
||||
{".mkv", "video/x-matroska"},
|
||||
{".mmf", "application/x-smaf"},
|
||||
{".mno", "text/xml"},
|
||||
{".mny", "application/x-msmoney"},
|
||||
{".mod", "video/mpeg"},
|
||||
{".mov", "video/quicktime"},
|
||||
{".movie", "video/x-sgi-movie"},
|
||||
{".mp2", "video/mpeg"},
|
||||
{".mp2v", "video/mpeg"},
|
||||
{".mp3", "audio/mpeg"},
|
||||
{".mp4", "video/mp4"},
|
||||
{".mp4v", "video/mp4"},
|
||||
{".mpa", "video/mpeg"},
|
||||
{".mpe", "video/mpeg"},
|
||||
{".mpeg", "video/mpeg"},
|
||||
{".mpf", "application/vnd.ms-mediapackage"},
|
||||
{".mpg", "video/mpeg"},
|
||||
{".mpp", "application/vnd.ms-project"},
|
||||
{".mpv2", "video/mpeg"},
|
||||
{".mqv", "video/quicktime"},
|
||||
{".ms", "application/x-troff-ms"},
|
||||
{".msg", "application/vnd.ms-outlook"},
|
||||
{".msi", "application/octet-stream"},
|
||||
{".mso", "application/octet-stream"},
|
||||
{".mts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".mtx", "application/xml"},
|
||||
{".mvb", "application/x-msmediaview"},
|
||||
{".mvc", "application/x-miva-compiled"},
|
||||
{".mxp", "application/x-mmxp"},
|
||||
{".nc", "application/x-netcdf"},
|
||||
{".nsc", "video/x-ms-asf"},
|
||||
{".nws", "message/rfc822"},
|
||||
{".ocx", "application/octet-stream"},
|
||||
{".oda", "application/oda"},
|
||||
{".odb", "application/vnd.oasis.opendocument.database"},
|
||||
{".odc", "application/vnd.oasis.opendocument.chart"},
|
||||
{".odf", "application/vnd.oasis.opendocument.formula"},
|
||||
{".odg", "application/vnd.oasis.opendocument.graphics"},
|
||||
{".odh", "text/plain"},
|
||||
{".odi", "application/vnd.oasis.opendocument.image"},
|
||||
{".odl", "text/plain"},
|
||||
{".odm", "application/vnd.oasis.opendocument.text-master"},
|
||||
{".odp", "application/vnd.oasis.opendocument.presentation"},
|
||||
{".ods", "application/vnd.oasis.opendocument.spreadsheet"},
|
||||
{".odt", "application/vnd.oasis.opendocument.text"},
|
||||
{".oga", "audio/ogg"},
|
||||
{".ogg", "audio/ogg"},
|
||||
{".ogv", "video/ogg"},
|
||||
{".ogx", "application/ogg"},
|
||||
{".one", "application/onenote"},
|
||||
{".onea", "application/onenote"},
|
||||
{".onepkg", "application/onenote"},
|
||||
{".onetmp", "application/onenote"},
|
||||
{".onetoc", "application/onenote"},
|
||||
{".onetoc2", "application/onenote"},
|
||||
{".opus", "audio/ogg"},
|
||||
{".orderedtest", "application/xml"},
|
||||
{".osdx", "application/opensearchdescription+xml"},
|
||||
{".otf", "application/font-sfnt"},
|
||||
{".otg", "application/vnd.oasis.opendocument.graphics-template"},
|
||||
{".oth", "application/vnd.oasis.opendocument.text-web"},
|
||||
{".otp", "application/vnd.oasis.opendocument.presentation-template"},
|
||||
{".ots", "application/vnd.oasis.opendocument.spreadsheet-template"},
|
||||
{".ott", "application/vnd.oasis.opendocument.text-template"},
|
||||
{".oxt", "application/vnd.openofficeorg.extension"},
|
||||
{".p10", "application/pkcs10"},
|
||||
{".p12", "application/x-pkcs12"},
|
||||
{".p7b", "application/x-pkcs7-certificates"},
|
||||
{".p7c", "application/pkcs7-mime"},
|
||||
{".p7m", "application/pkcs7-mime"},
|
||||
{".p7r", "application/x-pkcs7-certreqresp"},
|
||||
{".p7s", "application/pkcs7-signature"},
|
||||
{".pbm", "image/x-portable-bitmap"},
|
||||
{".pcast", "application/x-podcast"},
|
||||
{".pct", "image/pict"},
|
||||
{".pcx", "application/octet-stream"},
|
||||
{".pcz", "application/octet-stream"},
|
||||
{".pdf", "application/pdf"},
|
||||
{".pfb", "application/octet-stream"},
|
||||
{".pfm", "application/octet-stream"},
|
||||
{".pfx", "application/x-pkcs12"},
|
||||
{".pgm", "image/x-portable-graymap"},
|
||||
{".pic", "image/pict"},
|
||||
{".pict", "image/pict"},
|
||||
{".pkgdef", "text/plain"},
|
||||
{".pkgundef", "text/plain"},
|
||||
{".pko", "application/vnd.ms-pki.pko"},
|
||||
{".pls", "audio/scpls"},
|
||||
{".pma", "application/x-perfmon"},
|
||||
{".pmc", "application/x-perfmon"},
|
||||
{".pml", "application/x-perfmon"},
|
||||
{".pmr", "application/x-perfmon"},
|
||||
{".pmw", "application/x-perfmon"},
|
||||
{".png", "image/png"},
|
||||
{".pnm", "image/x-portable-anymap"},
|
||||
{".pnt", "image/x-macpaint"},
|
||||
{".pntg", "image/x-macpaint"},
|
||||
{".pnz", "image/png"},
|
||||
{".pot", "application/vnd.ms-powerpoint"},
|
||||
{".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"},
|
||||
{".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"},
|
||||
{".ppa", "application/vnd.ms-powerpoint"},
|
||||
{".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"},
|
||||
{".ppm", "image/x-portable-pixmap"},
|
||||
{".pps", "application/vnd.ms-powerpoint"},
|
||||
{".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
|
||||
{".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
|
||||
{".ppt", "application/vnd.ms-powerpoint"},
|
||||
{".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
|
||||
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
|
||||
{".prf", "application/pics-rules"},
|
||||
{".prm", "application/octet-stream"},
|
||||
{".prx", "application/octet-stream"},
|
||||
{".ps", "application/postscript"},
|
||||
{".psc1", "application/PowerShell"},
|
||||
{".psd", "application/octet-stream"},
|
||||
{".psess", "application/xml"},
|
||||
{".psm", "application/octet-stream"},
|
||||
{".psp", "application/octet-stream"},
|
||||
{".pst", "application/vnd.ms-outlook"},
|
||||
{".pub", "application/x-mspublisher"},
|
||||
{".pwz", "application/vnd.ms-powerpoint"},
|
||||
{".qht", "text/x-html-insertion"},
|
||||
{".qhtm", "text/x-html-insertion"},
|
||||
{".qt", "video/quicktime"},
|
||||
{".qti", "image/x-quicktime"},
|
||||
{".qtif", "image/x-quicktime"},
|
||||
{".qtl", "application/x-quicktimeplayer"},
|
||||
{".qxd", "application/octet-stream"},
|
||||
{".ra", "audio/x-pn-realaudio"},
|
||||
{".ram", "audio/x-pn-realaudio"},
|
||||
{".rar", "application/x-rar-compressed"},
|
||||
{".ras", "image/x-cmu-raster"},
|
||||
{".rat", "application/rat-file"},
|
||||
{".rc", "text/plain"},
|
||||
{".rc2", "text/plain"},
|
||||
{".rct", "text/plain"},
|
||||
{".rdlc", "application/xml"},
|
||||
{".reg", "text/plain"},
|
||||
{".resx", "application/xml"},
|
||||
{".rf", "image/vnd.rn-realflash"},
|
||||
{".rgb", "image/x-rgb"},
|
||||
{".rgs", "text/plain"},
|
||||
{".rm", "application/vnd.rn-realmedia"},
|
||||
{".rmi", "audio/mid"},
|
||||
{".rmp", "application/vnd.rn-rn_music_package"},
|
||||
{".roff", "application/x-troff"},
|
||||
{".rpm", "audio/x-pn-realaudio-plugin"},
|
||||
{".rqy", "text/x-ms-rqy"},
|
||||
{".rtf", "application/rtf"},
|
||||
{".rtx", "text/richtext"},
|
||||
{".rvt", "application/octet-stream" },
|
||||
{".ruleset", "application/xml"},
|
||||
{".s", "text/plain"},
|
||||
{".safariextz", "application/x-safari-safariextz"},
|
||||
{".scd", "application/x-msschedule"},
|
||||
{".scr", "text/plain"},
|
||||
{".sct", "text/scriptlet"},
|
||||
{".sd2", "audio/x-sd2"},
|
||||
{".sdp", "application/sdp"},
|
||||
{".sea", "application/octet-stream"},
|
||||
{".searchConnector-ms", "application/windows-search-connector+xml"},
|
||||
{".setpay", "application/set-payment-initiation"},
|
||||
{".setreg", "application/set-registration-initiation"},
|
||||
{".settings", "application/xml"},
|
||||
{".sgimb", "application/x-sgimb"},
|
||||
{".sgml", "text/sgml"},
|
||||
{".sh", "application/x-sh"},
|
||||
{".shar", "application/x-shar"},
|
||||
{".shtml", "text/html"},
|
||||
{".sit", "application/x-stuffit"},
|
||||
{".sitemap", "application/xml"},
|
||||
{".skin", "application/xml"},
|
||||
{".skp", "application/x-koan" },
|
||||
{".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"},
|
||||
{".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"},
|
||||
{".slk", "application/vnd.ms-excel"},
|
||||
{".sln", "text/plain"},
|
||||
{".slupkg-ms", "application/x-ms-license"},
|
||||
{".smd", "audio/x-smd"},
|
||||
{".smi", "application/octet-stream"},
|
||||
{".smx", "audio/x-smd"},
|
||||
{".smz", "audio/x-smd"},
|
||||
{".snd", "audio/basic"},
|
||||
{".snippet", "application/xml"},
|
||||
{".snp", "application/octet-stream"},
|
||||
{".sol", "text/plain"},
|
||||
{".sor", "text/plain"},
|
||||
{".spc", "application/x-pkcs7-certificates"},
|
||||
{".spl", "application/futuresplash"},
|
||||
{".spx", "audio/ogg"},
|
||||
{".src", "application/x-wais-source"},
|
||||
{".srf", "text/plain"},
|
||||
{".SSISDeploymentManifest", "text/xml"},
|
||||
{".ssm", "application/streamingmedia"},
|
||||
{".sst", "application/vnd.ms-pki.certstore"},
|
||||
{".stl", "application/vnd.ms-pki.stl"},
|
||||
{".sv4cpio", "application/x-sv4cpio"},
|
||||
{".sv4crc", "application/x-sv4crc"},
|
||||
{".svc", "application/xml"},
|
||||
{".svg", "image/svg+xml"},
|
||||
{".swf", "application/x-shockwave-flash"},
|
||||
{".step", "application/step"},
|
||||
{".stp", "application/step"},
|
||||
{".t", "application/x-troff"},
|
||||
{".tar", "application/x-tar"},
|
||||
{".tcl", "application/x-tcl"},
|
||||
{".testrunconfig", "application/xml"},
|
||||
{".testsettings", "application/xml"},
|
||||
{".tex", "application/x-tex"},
|
||||
{".texi", "application/x-texinfo"},
|
||||
{".texinfo", "application/x-texinfo"},
|
||||
{".tgz", "application/x-compressed"},
|
||||
{".thmx", "application/vnd.ms-officetheme"},
|
||||
{".thn", "application/octet-stream"},
|
||||
{".tif", "image/tiff"},
|
||||
{".tiff", "image/tiff"},
|
||||
{".tlh", "text/plain"},
|
||||
{".tli", "text/plain"},
|
||||
{".toc", "application/octet-stream"},
|
||||
{".tr", "application/x-troff"},
|
||||
{".trm", "application/x-msterminal"},
|
||||
{".trx", "application/xml"},
|
||||
{".ts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".tsv", "text/tab-separated-values"},
|
||||
{".ttf", "application/font-sfnt"},
|
||||
{".tts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".txt", "text/plain"},
|
||||
{".u32", "application/octet-stream"},
|
||||
{".uls", "text/iuls"},
|
||||
{".user", "text/plain"},
|
||||
{".ustar", "application/x-ustar"},
|
||||
{".vb", "text/plain"},
|
||||
{".vbdproj", "text/plain"},
|
||||
{".vbk", "video/mpeg"},
|
||||
{".vbproj", "text/plain"},
|
||||
{".vbs", "text/vbscript"},
|
||||
{".vcf", "text/x-vcard"},
|
||||
{".vcproj", "application/xml"},
|
||||
{".vcs", "text/plain"},
|
||||
{".vcxproj", "application/xml"},
|
||||
{".vddproj", "text/plain"},
|
||||
{".vdp", "text/plain"},
|
||||
{".vdproj", "text/plain"},
|
||||
{".vdx", "application/vnd.ms-visio.viewer"},
|
||||
{".vml", "text/xml"},
|
||||
{".vscontent", "application/xml"},
|
||||
{".vsct", "text/xml"},
|
||||
{".vsd", "application/vnd.visio"},
|
||||
{".vsi", "application/ms-vsi"},
|
||||
{".vsix", "application/vsix"},
|
||||
{".vsixlangpack", "text/xml"},
|
||||
{".vsixmanifest", "text/xml"},
|
||||
{".vsmdi", "application/xml"},
|
||||
{".vspscc", "text/plain"},
|
||||
{".vss", "application/vnd.visio"},
|
||||
{".vsscc", "text/plain"},
|
||||
{".vssettings", "text/xml"},
|
||||
{".vssscc", "text/plain"},
|
||||
{".vst", "application/vnd.visio"},
|
||||
{".vstemplate", "text/xml"},
|
||||
{".vsto", "application/x-ms-vsto"},
|
||||
{".vsw", "application/vnd.visio"},
|
||||
{".vsx", "application/vnd.visio"},
|
||||
{".vtt", "text/vtt"},
|
||||
{".vtx", "application/vnd.visio"},
|
||||
{".wasm", "application/wasm"},
|
||||
{".wav", "audio/wav"},
|
||||
{".wave", "audio/wav"},
|
||||
{".wax", "audio/x-ms-wax"},
|
||||
{".wbk", "application/msword"},
|
||||
{".wbmp", "image/vnd.wap.wbmp"},
|
||||
{".wcm", "application/vnd.ms-works"},
|
||||
{".wdb", "application/vnd.ms-works"},
|
||||
{".wdp", "image/vnd.ms-photo"},
|
||||
{".webarchive", "application/x-safari-webarchive"},
|
||||
{".webm", "video/webm"},
|
||||
{".webp", "image/webp"}, /* https://en.wikipedia.org/wiki/WebP */
|
||||
{".webtest", "application/xml"},
|
||||
{".wiq", "application/xml"},
|
||||
{".wiz", "application/msword"},
|
||||
{".wks", "application/vnd.ms-works"},
|
||||
{".WLMP", "application/wlmoviemaker"},
|
||||
{".wlpginstall", "application/x-wlpg-detect"},
|
||||
{".wlpginstall3", "application/x-wlpg3-detect"},
|
||||
{".wm", "video/x-ms-wm"},
|
||||
{".wma", "audio/x-ms-wma"},
|
||||
{".wmd", "application/x-ms-wmd"},
|
||||
{".wmf", "application/x-msmetafile"},
|
||||
{".wml", "text/vnd.wap.wml"},
|
||||
{".wmlc", "application/vnd.wap.wmlc"},
|
||||
{".wmls", "text/vnd.wap.wmlscript"},
|
||||
{".wmlsc", "application/vnd.wap.wmlscriptc"},
|
||||
{".wmp", "video/x-ms-wmp"},
|
||||
{".wmv", "video/x-ms-wmv"},
|
||||
{".wmx", "video/x-ms-wmx"},
|
||||
{".wmz", "application/x-ms-wmz"},
|
||||
{".woff", "application/font-woff"},
|
||||
{".woff2", "application/font-woff2"},
|
||||
{".wpl", "application/vnd.ms-wpl"},
|
||||
{".wps", "application/vnd.ms-works"},
|
||||
{".wri", "application/x-mswrite"},
|
||||
{".wrl", "x-world/x-vrml"},
|
||||
{".wrz", "x-world/x-vrml"},
|
||||
{".wsc", "text/scriptlet"},
|
||||
{".wsdl", "text/xml"},
|
||||
{".wvx", "video/x-ms-wvx"},
|
||||
{".x", "application/directx"},
|
||||
{".xaf", "x-world/x-vrml"},
|
||||
{".xaml", "application/xaml+xml"},
|
||||
{".xap", "application/x-silverlight-app"},
|
||||
{".xbap", "application/x-ms-xbap"},
|
||||
{".xbm", "image/x-xbitmap"},
|
||||
{".xdr", "text/plain"},
|
||||
{".xht", "application/xhtml+xml"},
|
||||
{".xhtml", "application/xhtml+xml"},
|
||||
{".xla", "application/vnd.ms-excel"},
|
||||
{".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"},
|
||||
{".xlc", "application/vnd.ms-excel"},
|
||||
{".xld", "application/vnd.ms-excel"},
|
||||
{".xlk", "application/vnd.ms-excel"},
|
||||
{".xll", "application/vnd.ms-excel"},
|
||||
{".xlm", "application/vnd.ms-excel"},
|
||||
{".xls", "application/vnd.ms-excel"},
|
||||
{".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
|
||||
{".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"},
|
||||
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
|
||||
{".xlt", "application/vnd.ms-excel"},
|
||||
{".xltm", "application/vnd.ms-excel.template.macroEnabled.12"},
|
||||
{".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
|
||||
{".xlw", "application/vnd.ms-excel"},
|
||||
{".xml", "text/xml"},
|
||||
{".xmp", "application/octet-stream" },
|
||||
{".xmta", "application/xml"},
|
||||
{".xof", "x-world/x-vrml"},
|
||||
{".XOML", "text/plain"},
|
||||
{".xpm", "image/x-xpixmap"},
|
||||
{".xps", "application/vnd.ms-xpsdocument"},
|
||||
{".xrm-ms", "text/xml"},
|
||||
{".xsc", "application/xml"},
|
||||
{".xsd", "text/xml"},
|
||||
{".xsf", "text/xml"},
|
||||
{".xsl", "text/xml"},
|
||||
{".xslt", "text/xml"},
|
||||
{".xsn", "application/octet-stream"},
|
||||
{".xss", "application/xml"},
|
||||
{".xspf", "application/xspf+xml"},
|
||||
{".xtp", "application/octet-stream"},
|
||||
{".xwd", "image/x-xwindowdump"},
|
||||
{".z", "application/x-compress"},
|
||||
{".zip", "application/zip"},
|
||||
};
|
||||
}
|
||||
}
|
||||
174
Vendor/EmbedIO-3.5.2/MimeType.cs
vendored
Normal file
174
Vendor/EmbedIO-3.5.2/MimeType.cs
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides constants for commonly-used MIME types and association between file extensions and MIME types.
|
||||
/// </summary>
|
||||
/// <seealso cref="Associations"/>
|
||||
public static partial class MimeType
|
||||
{
|
||||
/// <summary>
|
||||
/// The default MIME type for data whose type is unknown,
|
||||
/// i.e. <c>application/octet-stream</c>.
|
||||
/// </summary>
|
||||
public const string Default = "application/octet-stream";
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type for plain text, i.e. <c>text/plain</c>.
|
||||
/// </summary>
|
||||
public const string PlainText = "text/plain";
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type for HTML, i.e. <c>text/html</c>.
|
||||
/// </summary>
|
||||
public const string Html = "text/html";
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type for JSON, i.e. <c>application/json</c>.
|
||||
/// </summary>
|
||||
public const string Json = "application/json";
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type for URL-encoded HTML forms,
|
||||
/// i.e. <c>application/x-www-form-urlencoded</c>.
|
||||
/// </summary>
|
||||
internal const string UrlEncodedForm = "application/x-www-form-urlencoded";
|
||||
|
||||
/// <summary>
|
||||
/// <para>Strips parameters, if present (e.g. <c>; encoding=UTF-8</c>), from a MIME type.</para>
|
||||
/// </summary>
|
||||
/// <param name="value">The MIME type.</param>
|
||||
/// <returns><paramref name="value"/> without parameters.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method does not validate <paramref name="value"/>: if it is not
|
||||
/// a valid MIME type or media range, it is just returned unchanged.</para>
|
||||
/// </remarks>
|
||||
public static string StripParameters(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return value;
|
||||
|
||||
var semicolonPos = value.IndexOf(';');
|
||||
return semicolonPos < 0
|
||||
? value
|
||||
: value.Substring(0, semicolonPos).TrimEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified string is a valid MIME type or media range.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="acceptMediaRange">If set to <see langword="true"/>, both media ranges
|
||||
/// (e.g. <c>"text/*"</c>, <c>"*/*"</c>) and specific MIME types (e.g. <c>"text/html"</c>)
|
||||
/// are considered valid; if set to <see langword="false"/>, only specific MIME types
|
||||
/// are considered valid.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="value"/> is valid,
|
||||
/// according to the value of <paramref name="acceptMediaRange"/>;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
public static bool IsMimeType(string value, bool acceptMediaRange)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return false;
|
||||
|
||||
var slashPos = value.IndexOf('/');
|
||||
if (slashPos < 0)
|
||||
return false;
|
||||
|
||||
var isWildcardSubtype = false;
|
||||
var subtype = value.Substring(slashPos + 1);
|
||||
if (subtype == "*")
|
||||
{
|
||||
if (!acceptMediaRange)
|
||||
return false;
|
||||
|
||||
isWildcardSubtype = true;
|
||||
}
|
||||
else if (!Validate.IsRfc2616Token(subtype))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var type = value.Substring(0, slashPos);
|
||||
return type == "*"
|
||||
? acceptMediaRange && isWildcardSubtype
|
||||
: Validate.IsRfc2616Token(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the specified MIME type or media range into type and subtype.
|
||||
/// </summary>
|
||||
/// <param name="mimeType">The MIME type or media range to split.</param>
|
||||
/// <returns>A tuple of type and subtype.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="mimeType"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="mimeType"/> is not a valid
|
||||
/// MIME type or media range.</exception>
|
||||
public static (string type, string subtype) Split(string mimeType)
|
||||
=> UnsafeSplit(Validate.MimeType(nameof(mimeType), mimeType, true));
|
||||
|
||||
/// <summary>
|
||||
/// Matches the specified MIME type to a media range.
|
||||
/// </summary>
|
||||
/// <param name="mimeType">The MIME type to match.</param>
|
||||
/// <param name="mediaRange">The media range.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="mediaRange"/> is either
|
||||
/// the same as <paramref name="mimeType"/>, or has the same type and a subtype
|
||||
/// of <c>"*"</c>, or is <c>"*/*"</c>.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="mimeType"/> is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mediaRange"/> is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="mimeType"/> is not a valid MIME type.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mediaRange"/> is not a valid MIME media range.</para>
|
||||
/// </exception>
|
||||
public static bool IsInRange(string mimeType, string mediaRange)
|
||||
=> UnsafeIsInRange(
|
||||
Validate.MimeType(nameof(mimeType), mimeType, false),
|
||||
Validate.MimeType(nameof(mediaRange), mediaRange, true));
|
||||
|
||||
internal static (string type, string subtype) UnsafeSplit(string mimeType)
|
||||
{
|
||||
var slashPos = mimeType.IndexOf('/');
|
||||
return (mimeType.Substring(0, slashPos), mimeType.Substring(slashPos + 1));
|
||||
}
|
||||
|
||||
internal static bool UnsafeIsInRange(string mimeType, string mediaRange)
|
||||
{
|
||||
// A validated media range that starts with '*' can only be '*/*'
|
||||
if (mediaRange[0] == '*')
|
||||
return true;
|
||||
|
||||
var typeSlashPos = mimeType.IndexOf('/');
|
||||
var rangeSlashPos = mediaRange.IndexOf('/');
|
||||
|
||||
if (typeSlashPos != rangeSlashPos)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < typeSlashPos; i++)
|
||||
{
|
||||
if (mimeType[i] != mediaRange[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
// A validated token has at least 1 character,
|
||||
// thus there must be at least 1 character after a slash.
|
||||
if (mediaRange[rangeSlashPos + 1] == '*')
|
||||
return true;
|
||||
|
||||
if (mimeType.Length != mediaRange.Length)
|
||||
return false;
|
||||
|
||||
for (var i = typeSlashPos + 1; i < mimeType.Length; i++)
|
||||
{
|
||||
if (mimeType[i] != mediaRange[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
Vendor/EmbedIO-3.5.2/MimeTypeCustomizerExtensions.cs
vendored
Normal file
99
Vendor/EmbedIO-3.5.2/MimeTypeCustomizerExtensions.cs
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for types implementing <see cref="IMimeTypeCustomizer"/>.
|
||||
/// </summary>
|
||||
public static class MimeTypeCustomizerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a custom association between a file extension and a MIME type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to which this method is applied.</typeparam>
|
||||
/// <param name="this">The object to which this method is applied.</param>
|
||||
/// <param name="extension">The file extension to associate to <paramref name="mimeType"/>.</param>
|
||||
/// <param name="mimeType">The MIME type to associate to <paramref name="extension"/>.</param>
|
||||
/// <returns><paramref name="this"/> with the custom association added.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="this"/> has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="extension"/>is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mimeType"/>is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="extension"/>is the empty string.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mimeType"/>is not a valid MIME type.</para>
|
||||
/// </exception>
|
||||
public static T WithCustomMimeType<T>(this T @this, string extension, string mimeType)
|
||||
where T : IMimeTypeCustomizer
|
||||
{
|
||||
@this.AddCustomMimeType(extension, mimeType);
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether to prefer compression when negotiating content encoding
|
||||
/// for a response with the specified content type, or whose content type is in
|
||||
/// the specified media range.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to which this method is applied.</typeparam>
|
||||
/// <param name="this">The object to which this method is applied.</param>
|
||||
/// <param name="mimeType">The MIME type or media range.</param>
|
||||
/// <param name="preferCompression"><see langword="true"/> to prefer compression;
|
||||
/// otherwise, <see langword="false"/>.</param>
|
||||
/// <returns><paramref name="this"/> with the specified preference added.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="this"/> has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="mimeType"/>is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="mimeType"/>is not a valid MIME type or media range.</exception>
|
||||
public static T PreferCompressionFor<T>(this T @this, string mimeType, bool preferCompression)
|
||||
where T : IMimeTypeCustomizer
|
||||
{
|
||||
@this.PreferCompression(mimeType, preferCompression);
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that compression should be preferred when negotiating content encoding
|
||||
/// for a response with the specified content type, or whose content type is in
|
||||
/// the specified media range.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to which this method is applied.</typeparam>
|
||||
/// <param name="this">The object to which this method is applied.</param>
|
||||
/// <param name="mimeType">The MIME type or media range.</param>
|
||||
/// <returns><paramref name="this"/> with the specified preference added.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="this"/> has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="mimeType"/>is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="mimeType"/>is not a valid MIME type or media range.</exception>
|
||||
public static T PreferCompressionFor<T>(this T @this, string mimeType)
|
||||
where T : IMimeTypeCustomizer
|
||||
{
|
||||
@this.PreferCompression(mimeType, true);
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that no compression should be preferred when negotiating content encoding
|
||||
/// for a response with the specified content type, or whose content type is in
|
||||
/// the specified media range.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to which this method is applied.</typeparam>
|
||||
/// <param name="this">The object to which this method is applied.</param>
|
||||
/// <param name="mimeType">The MIME type or media range.</param>
|
||||
/// <returns><paramref name="this"/> with the specified preference added.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="this"/> has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="mimeType"/>is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="mimeType"/>is not a valid MIME type or media range.</exception>
|
||||
public static T PreferNoCompressionFor<T>(this T @this, string mimeType)
|
||||
where T : IMimeTypeCustomizer
|
||||
{
|
||||
@this.PreferCompression(mimeType, false);
|
||||
return @this;
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Vendor/EmbedIO-3.5.2/ModuleGroup.cs
vendored
Normal file
111
Vendor/EmbedIO-3.5.2/ModuleGroup.cs
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Groups modules under a common base URL path.</para>
|
||||
/// <para>The <see cref="IWebModule.BaseRoute">BaseRoute</see> property
|
||||
/// of modules contained in a <c>ModuleGroup</c> is relative to the
|
||||
/// <c>ModuleGroup</c>'s <see cref="IWebModule.BaseRoute">BaseRoute</see> property.
|
||||
/// For example, given the following code:</para>
|
||||
/// <para><code>new ModuleGroup("/download")
|
||||
/// .WithStaticFilesAt("/docs", "/var/my/documents");</code></para>
|
||||
/// <para>files contained in the <c>/var/my/documents</c> folder will be
|
||||
/// available to clients under the <c>/download/docs/</c> URL.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="WebModuleBase" />
|
||||
/// <seealso cref="IDisposable" />
|
||||
/// <seealso cref="IWebModuleContainer" />
|
||||
public class ModuleGroup : WebModuleBase, IDisposable, IWebModuleContainer, IMimeTypeCustomizer
|
||||
{
|
||||
private readonly WebModuleCollection _modules;
|
||||
private readonly MimeTypeCustomizer _mimeTypeCustomizer = new MimeTypeCustomizer();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModuleGroup" /> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route served by this module.</param>
|
||||
/// <param name="isFinalHandler">The value to set the <see cref="IWebModule.IsFinalHandler" /> property to.
|
||||
/// See the help for the property for more information.</param>
|
||||
/// <seealso cref="IWebModule.BaseRoute" />
|
||||
/// <seealso cref="IWebModule.IsFinalHandler" />
|
||||
public ModuleGroup(string baseRoute, bool isFinalHandler)
|
||||
: base(baseRoute)
|
||||
{
|
||||
IsFinalHandler = isFinalHandler;
|
||||
_modules = new WebModuleCollection(nameof(ModuleGroup));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ModuleGroup"/> class.
|
||||
/// </summary>
|
||||
~ModuleGroup()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsFinalHandler { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IComponentCollection<IWebModule> Modules => _modules;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
string IMimeTypeProvider.GetMimeType(string extension)
|
||||
=> _mimeTypeCustomizer.GetMimeType(extension);
|
||||
|
||||
bool IMimeTypeProvider.TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
=> _mimeTypeCustomizer.TryDetermineCompression(mimeType, out preferCompression);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddCustomMimeType(string extension, string mimeType)
|
||||
=> _mimeTypeCustomizer.AddCustomMimeType(extension, mimeType);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PreferCompression(string mimeType, bool preferCompression)
|
||||
=> _mimeTypeCustomizer.PreferCompression(mimeType, preferCompression);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task OnRequestAsync(IHttpContext context)
|
||||
=> _modules.DispatchRequestAsync(context);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources;
|
||||
/// <see langword="false"/> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_modules.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnBeforeLockConfiguration()
|
||||
{
|
||||
base.OnBeforeLockConfiguration();
|
||||
|
||||
_mimeTypeCustomizer.Lock();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnStart(CancellationToken cancellationToken)
|
||||
{
|
||||
_modules.StartAll(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
243
Vendor/EmbedIO-3.5.2/Net/CookieList.cs
vendored
Normal file
243
Vendor/EmbedIO-3.5.2/Net/CookieList.cs
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Net.Internal;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Provides a collection container for instances of <see cref="Cookie"/>.</para>
|
||||
/// <para>This class is meant to be used internally by EmbedIO; you don't need to
|
||||
/// use this class directly.</para>
|
||||
/// </summary>
|
||||
#pragma warning disable CA1710 // Rename class to end in 'Collection' - it ends in 'List', i.e. 'Indexed Collection'.
|
||||
public sealed class CookieList : List<Cookie>, ICookieCollection
|
||||
#pragma warning restore CA1710
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool IsSynchronized => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Cookie? this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
if (Count == 0)
|
||||
return null;
|
||||
|
||||
var list = new List<Cookie>(this);
|
||||
|
||||
list.Sort(CompareCookieWithinSorted);
|
||||
|
||||
return list.FirstOrDefault(cookie => cookie.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a <see cref="CookieList"/> by parsing
|
||||
/// the value of one or more <c>Cookie</c> or <c>Set-Cookie</c> headers.</summary>
|
||||
/// <param name="headerValue">The value, or comma-separated list of values,
|
||||
/// of the header or headers.</param>
|
||||
/// <returns>A newly-created instance of <see cref="CookieList"/>.</returns>
|
||||
public static CookieList Parse(string headerValue)
|
||||
{
|
||||
var cookies = new CookieList();
|
||||
|
||||
Cookie? cookie = null;
|
||||
var pairs = SplitCookieHeaderValue(headerValue);
|
||||
|
||||
for (var i = 0; i < pairs.Length; i++)
|
||||
{
|
||||
var pair = pairs[i].Trim();
|
||||
if (pair.Length == 0)
|
||||
continue;
|
||||
|
||||
if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Version = int.Parse(GetValue(pair, true), CultureInfo.InvariantCulture);
|
||||
}
|
||||
else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
var buff = new StringBuilder(GetValue(pair), 32);
|
||||
if (i < pairs.Length - 1)
|
||||
buff.AppendFormat(CultureInfo.InvariantCulture, ", {0}", pairs[++i].Trim());
|
||||
|
||||
if (!HttpDate.TryParse(buff.ToString(), out var expires))
|
||||
expires = DateTimeOffset.Now;
|
||||
|
||||
if (cookie.Expires == DateTime.MinValue)
|
||||
cookie.Expires = expires.LocalDateTime;
|
||||
}
|
||||
else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
var max = int.Parse(GetValue(pair, true), CultureInfo.InvariantCulture);
|
||||
|
||||
cookie.Expires = DateTime.Now.AddSeconds(max);
|
||||
}
|
||||
else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Path = GetValue(pair);
|
||||
}
|
||||
else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Domain = GetValue(pair);
|
||||
}
|
||||
else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Port = pair.Equals("port", StringComparison.OrdinalIgnoreCase)
|
||||
? "\"\""
|
||||
: GetValue(pair);
|
||||
}
|
||||
else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Comment = WebUtility.UrlDecode(GetValue(pair));
|
||||
}
|
||||
else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.CommentUri = UriUtility.StringToUri(GetValue(pair, true));
|
||||
}
|
||||
else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Discard = true;
|
||||
}
|
||||
else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Secure = true;
|
||||
}
|
||||
else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.HttpOnly = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cookie != null)
|
||||
cookies.Add(cookie);
|
||||
|
||||
cookie = ParseCookie(pair);
|
||||
}
|
||||
}
|
||||
|
||||
if (cookie != null)
|
||||
cookies.Add(cookie);
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public new void Add(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
|
||||
var pos = SearchCookie(cookie);
|
||||
if (pos == -1)
|
||||
{
|
||||
base.Add(cookie);
|
||||
return;
|
||||
}
|
||||
|
||||
this[pos] = cookie;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
if (array == null)
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
|
||||
|
||||
if (array.Rank > 1)
|
||||
throw new ArgumentException("Multidimensional.", nameof(array));
|
||||
|
||||
if (array.Length - index < Count)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The number of elements in this collection is greater than the available space of the destination array.");
|
||||
}
|
||||
|
||||
if (array.GetType().GetElementType()?.IsAssignableFrom(typeof(Cookie)) != true)
|
||||
{
|
||||
throw new InvalidCastException(
|
||||
"The elements in this collection cannot be cast automatically to the type of the destination array.");
|
||||
}
|
||||
|
||||
((IList) this).CopyTo(array, index);
|
||||
}
|
||||
|
||||
private static string? GetValue(string nameAndValue, bool unquote = false)
|
||||
{
|
||||
var idx = nameAndValue.IndexOf('=');
|
||||
|
||||
if (idx < 0 || idx == nameAndValue.Length - 1)
|
||||
return null;
|
||||
|
||||
var val = nameAndValue.Substring(idx + 1).Trim();
|
||||
return unquote ? val.Unquote() : val;
|
||||
}
|
||||
|
||||
private static string[] SplitCookieHeaderValue(string value) => value.SplitHeaderValue(true).ToArray();
|
||||
|
||||
private static int CompareCookieWithinSorted(Cookie x, Cookie y)
|
||||
{
|
||||
var ret = x.Version - y.Version;
|
||||
return ret != 0
|
||||
? ret
|
||||
: (ret = string.Compare(x.Name, y.Name, StringComparison.Ordinal)) != 0
|
||||
? ret
|
||||
: y.Path.Length - x.Path.Length;
|
||||
}
|
||||
|
||||
private static Cookie ParseCookie(string pair)
|
||||
{
|
||||
string name;
|
||||
var val = string.Empty;
|
||||
|
||||
var pos = pair.IndexOf('=');
|
||||
if (pos == -1)
|
||||
{
|
||||
name = pair;
|
||||
}
|
||||
else if (pos == pair.Length - 1)
|
||||
{
|
||||
name = pair.Substring(0, pos).TrimEnd(' ');
|
||||
}
|
||||
else
|
||||
{
|
||||
name = pair.Substring(0, pos).TrimEnd(' ');
|
||||
val = pair.Substring(pos + 1).TrimStart(' ');
|
||||
}
|
||||
|
||||
return new Cookie(name, val);
|
||||
}
|
||||
|
||||
private int SearchCookie(Cookie cookie)
|
||||
{
|
||||
var name = cookie.Name;
|
||||
var path = cookie.Path;
|
||||
var domain = cookie.Domain;
|
||||
var ver = cookie.Version;
|
||||
|
||||
for (var i = Count - 1; i >= 0; i--)
|
||||
{
|
||||
var c = this[i];
|
||||
if (c.Name.Equals(name, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.Path.Equals(path, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.Domain.Equals(domain, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.Version == ver)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
141
Vendor/EmbedIO-3.5.2/Net/EndPointManager.cs
vendored
Normal file
141
Vendor/EmbedIO-3.5.2/Net/EndPointManager.cs
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using EmbedIO.Net.Internal;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the EndPoint Manager.
|
||||
/// </summary>
|
||||
public static class EndPointManager
|
||||
{
|
||||
private static readonly ConcurrentDictionary<IPAddress, ConcurrentDictionary<int, EndPointListener>> IPToEndpoints = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [use IPv6]. By default, this flag is set.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [use IPv6]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public static bool UseIpv6 { get; set; } = true;
|
||||
|
||||
internal static void AddListener(HttpListener listener)
|
||||
{
|
||||
var added = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var prefix in listener.Prefixes)
|
||||
{
|
||||
AddPrefix(prefix, listener);
|
||||
added.Add(prefix);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.Log(nameof(AddListener));
|
||||
|
||||
foreach (var prefix in added)
|
||||
{
|
||||
RemovePrefix(prefix, listener);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RemoveEndPoint(EndPointListener epl, IPEndPoint ep)
|
||||
{
|
||||
if (IPToEndpoints.TryGetValue(ep.Address, out var p))
|
||||
{
|
||||
if (p.TryRemove(ep.Port, out _) && p.Count == 0)
|
||||
{
|
||||
_ = IPToEndpoints.TryRemove(ep.Address, out _);
|
||||
}
|
||||
}
|
||||
|
||||
epl.Dispose();
|
||||
}
|
||||
|
||||
internal static void RemoveListener(HttpListener listener)
|
||||
{
|
||||
foreach (var prefix in listener.Prefixes)
|
||||
{
|
||||
RemovePrefix(prefix, listener);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void AddPrefix(string p, HttpListener listener)
|
||||
{
|
||||
var lp = new ListenerPrefix(p);
|
||||
|
||||
if (!lp.IsValid())
|
||||
{
|
||||
throw new HttpListenerException(400, "Invalid path.");
|
||||
}
|
||||
|
||||
// listens on all the interfaces if host name cannot be parsed by IPAddress.
|
||||
var epl = GetEpListener(lp.Host, lp.Port, listener, lp.Secure);
|
||||
epl.AddPrefix(lp, listener);
|
||||
}
|
||||
|
||||
private static EndPointListener GetEpListener(string host, int port, HttpListener listener, bool secure = false)
|
||||
{
|
||||
var address = ResolveAddress(host);
|
||||
|
||||
var p = IPToEndpoints.GetOrAdd(address, x => new ConcurrentDictionary<int, EndPointListener>());
|
||||
return p.GetOrAdd(port, x => new EndPointListener(listener, address, x, secure));
|
||||
}
|
||||
|
||||
private static IPAddress ResolveAddress(string host)
|
||||
{
|
||||
if (host == "*" || host == "+" || host == "0.0.0.0")
|
||||
{
|
||||
return UseIpv6 ? IPAddress.IPv6Any : IPAddress.Any;
|
||||
}
|
||||
|
||||
if (IPAddress.TryParse(host, out var address))
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var hostEntry = new IPHostEntry {
|
||||
HostName = host,
|
||||
AddressList = Dns.GetHostAddresses(host),
|
||||
};
|
||||
|
||||
return hostEntry.AddressList[0];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return UseIpv6 ? IPAddress.IPv6Any : IPAddress.Any;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemovePrefix(string prefix, HttpListener listener)
|
||||
{
|
||||
try
|
||||
{
|
||||
var lp = new ListenerPrefix(prefix);
|
||||
|
||||
if (!lp.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var epl = GetEpListener(lp.Host, lp.Port, listener, lp.Secure);
|
||||
epl.RemovePrefix(lp);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user