mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-06-03 05:34:16 -04:00
update portable projects
This commit is contained in:
parent
f8b8de13b7
commit
406e6cb813
@ -8,9 +8,6 @@ using MediaBrowser.Model.Logging;
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Common.IO;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.IO;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace Emby.Drawing.ImageMagick
|
namespace Emby.Drawing.ImageMagick
|
||||||
@ -19,17 +16,15 @@ namespace Emby.Drawing.ImageMagick
|
|||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly Func<IHttpClient> _httpClientFactory;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IServerConfigurationManager _config;
|
|
||||||
|
|
||||||
public ImageMagickEncoder(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config)
|
public ImageMagickEncoder(ILogger logger, IApplicationPaths appPaths, Func<IHttpClient> httpClientFactory, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_config = config;
|
|
||||||
|
|
||||||
LogVersion();
|
LogVersion();
|
||||||
}
|
}
|
||||||
@ -260,7 +255,7 @@ namespace Emby.Drawing.ImageMagick
|
|||||||
{
|
{
|
||||||
var currentImageSize = new ImageSize(imageWidth, imageHeight);
|
var currentImageSize = new ImageSize(imageWidth, imageHeight);
|
||||||
|
|
||||||
var task = new PlayedIndicatorDrawer(_appPaths, _httpClient, _fileSystem).DrawPlayedIndicator(wand, currentImageSize);
|
var task = new PlayedIndicatorDrawer(_appPaths, _httpClientFactory(), _fileSystem).DrawPlayedIndicator(wand, currentImageSize);
|
||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
else if (options.UnplayedCount.HasValue)
|
else if (options.UnplayedCount.HasValue)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ServiceStack\ServiceStack.csproj" />
|
||||||
<ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj" />
|
<ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj" />
|
||||||
<ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
|
<ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"version": "1.0.0-*",
|
"version": "1.0.0-*",
|
||||||
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
@ -56,65 +56,75 @@
|
|||||||
"Emby.Drawing": {
|
"Emby.Drawing": {
|
||||||
"target": "project"
|
"target": "project"
|
||||||
},
|
},
|
||||||
"SocketHttpListener.Portable": "1.0.50"
|
"ServiceStack": {
|
||||||
}
|
|
||||||
},
|
|
||||||
"netstandard1.6": {
|
|
||||||
"imports": "dnxcore50",
|
|
||||||
"dependencies": {
|
|
||||||
"NETStandard.Library": "1.6.0",
|
|
||||||
"System.AppDomain": "2.0.11",
|
|
||||||
"System.Globalization.Extensions": "4.0.1",
|
|
||||||
"System.IO.FileSystem.Watcher": "4.0.0",
|
|
||||||
"System.Net.Security": "4.0.0",
|
|
||||||
"System.Security.Cryptography.X509Certificates": "4.1.0",
|
|
||||||
"System.Runtime.Extensions": "4.1.0",
|
|
||||||
"SocketHttpListener.Portable": "1.0.50",
|
|
||||||
"MediaBrowser.Model": {
|
|
||||||
"target": "project"
|
"target": "project"
|
||||||
},
|
},
|
||||||
"MediaBrowser.Common": {
|
"SocketHttpListener.Portable": {
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"MediaBrowser.Controller": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"Emby.Common.Implementations": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"Mono.Nat": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"Emby.Server.Implementations": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"MediaBrowser.Server.Implementations": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"Emby.Dlna": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"Emby.Photos": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"MediaBrowser.Api": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"MediaBrowser.MediaEncoding": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"MediaBrowser.XbmcMetadata": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"MediaBrowser.LocalMetadata": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"MediaBrowser.WebDashboard": {
|
|
||||||
"target": "project"
|
|
||||||
},
|
|
||||||
"Emby.Drawing": {
|
|
||||||
"target": "project"
|
"target": "project"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"netstandard1.6": {
|
||||||
|
"imports": "dnxcore50",
|
||||||
|
"dependencies": {
|
||||||
|
"NETStandard.Library": "1.6.0",
|
||||||
|
"System.AppDomain": "2.0.11",
|
||||||
|
"System.Globalization.Extensions": "4.0.1",
|
||||||
|
"System.IO.FileSystem.Watcher": "4.0.0",
|
||||||
|
"System.Net.Security": "4.0.0",
|
||||||
|
"System.Security.Cryptography.X509Certificates": "4.1.0",
|
||||||
|
"System.Runtime.Extensions": "4.1.0",
|
||||||
|
"MediaBrowser.Model": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"MediaBrowser.Common": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"MediaBrowser.Controller": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"Emby.Common.Implementations": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"Mono.Nat": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"Emby.Server.Implementations": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"MediaBrowser.Server.Implementations": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"Emby.Dlna": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"Emby.Photos": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"MediaBrowser.Api": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"MediaBrowser.MediaEncoding": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"MediaBrowser.XbmcMetadata": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"MediaBrowser.LocalMetadata": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"MediaBrowser.WebDashboard": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"Emby.Drawing": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"SocketHttpListener.Portable": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"ServiceStack": {
|
||||||
|
"target": "project"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,6 @@
|
|||||||
<Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
|
<Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\QueueStream.cs" />
|
<Compile Include="LiveTv\TunerHosts\QueueStream.cs" />
|
||||||
<Compile Include="Localization\LocalizationManager.cs" />
|
<Compile Include="Localization\LocalizationManager.cs" />
|
||||||
<Compile Include="Logging\PatternsLogger.cs" />
|
|
||||||
<Compile Include="MediaEncoder\EncodingManager.cs" />
|
<Compile Include="MediaEncoder\EncodingManager.cs" />
|
||||||
<Compile Include="News\NewsEntryPoint.cs" />
|
<Compile Include="News\NewsEntryPoint.cs" />
|
||||||
<Compile Include="News\NewsService.cs" />
|
<Compile Include="News\NewsService.cs" />
|
||||||
@ -275,25 +274,26 @@
|
|||||||
<Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project>
|
<Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project>
|
||||||
<Name>MediaBrowser.Server.Implementations</Name>
|
<Name>MediaBrowser.Server.Implementations</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<Reference Include="ServiceStack">
|
<ProjectReference Include="..\ServiceStack\ServiceStack.csproj">
|
||||||
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.dll</HintPath>
|
<Project>{680a1709-25eb-4d52-a87f-ee03ffd94baa}</Project>
|
||||||
|
<Name>ServiceStack</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\SocketHttpListener.Portable\SocketHttpListener.Portable.csproj">
|
||||||
|
<Project>{4f26d5d8-a7b0-42b3-ba42-7cb7d245934e}</Project>
|
||||||
|
<Name>SocketHttpListener.Portable</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<Reference Include="Emby.XmlTv, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Emby.XmlTv.1.0.1\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="MediaBrowser.Naming, Version=1.0.6159.25070, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\MediaBrowser.Naming.1.0.2\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="UniversalDetector, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="UniversalDetector, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\UniversalDetector.1.0.1\lib\portable-net45+sl4+wp71+win8+wpa81\UniversalDetector.dll</HintPath>
|
<HintPath>..\packages\UniversalDetector.1.0.1\lib\portable-net45+sl4+wp71+win8+wpa81\UniversalDetector.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Emby.XmlTv, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\Emby.XmlTv.1.0.0.63\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="MediaBrowser.Naming, Version=1.0.6151.30291, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.59\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Patterns.Logging, Version=1.0.6151.30227, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\Patterns.Logging.1.0.0.6\lib\portable-net45+win8\Patterns.Logging.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Localization\Core\ar.json" />
|
<EmbeddedResource Include="Localization\Core\ar.json" />
|
||||||
|
@ -15,7 +15,6 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Library;
|
using Emby.Server.Implementations.Library;
|
||||||
using Emby.Server.Implementations.Logging;
|
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.IO;
|
using MediaBrowser.Controller.IO;
|
||||||
@ -71,7 +70,7 @@ namespace Emby.Server.Implementations.FileOrganization
|
|||||||
}
|
}
|
||||||
|
|
||||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
||||||
var resolver = new EpisodeResolver(namingOptions, new PatternsLogger());
|
var resolver = new EpisodeResolver(namingOptions, new NullLogger());
|
||||||
|
|
||||||
var episodeInfo = resolver.Resolve(path, false) ??
|
var episodeInfo = resolver.Resolve(path, false) ??
|
||||||
new MediaBrowser.Naming.TV.EpisodeInfo();
|
new MediaBrowser.Naming.TV.EpisodeInfo();
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Logging;
|
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
@ -55,7 +54,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
|||||||
public void Start(IEnumerable<string> urlPrefixes)
|
public void Start(IEnumerable<string> urlPrefixes)
|
||||||
{
|
{
|
||||||
if (_listener == null)
|
if (_listener == null)
|
||||||
_listener = new HttpListener(new PatternsLogger(_logger), _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider);
|
_listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider);
|
||||||
|
|
||||||
_listener.EnableDualMode = _enableDualMode;
|
_listener.EnableDualMode = _enableDualMode;
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Library.Resolvers;
|
using Emby.Server.Implementations.Library.Resolvers;
|
||||||
using Emby.Server.Implementations.Library.Validators;
|
using Emby.Server.Implementations.Library.Validators;
|
||||||
using Emby.Server.Implementations.Logging;
|
|
||||||
using Emby.Server.Implementations.ScheduledTasks;
|
using Emby.Server.Implementations.ScheduledTasks;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
@ -2266,7 +2265,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public bool IsVideoFile(string path, LibraryOptions libraryOptions)
|
public bool IsVideoFile(string path, LibraryOptions libraryOptions)
|
||||||
{
|
{
|
||||||
var resolver = new VideoResolver(GetNamingOptions(libraryOptions), new PatternsLogger());
|
var resolver = new VideoResolver(GetNamingOptions(libraryOptions), new NullLogger());
|
||||||
return resolver.IsVideoFile(path);
|
return resolver.IsVideoFile(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2294,7 +2293,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
public bool FillMissingEpisodeNumbersFromPath(Episode episode)
|
public bool FillMissingEpisodeNumbersFromPath(Episode episode)
|
||||||
{
|
{
|
||||||
var resolver = new EpisodeResolver(GetNamingOptions(),
|
var resolver = new EpisodeResolver(GetNamingOptions(),
|
||||||
new PatternsLogger());
|
new NullLogger());
|
||||||
|
|
||||||
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd ||
|
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd ||
|
||||||
episode.VideoType == VideoType.HdDvd;
|
episode.VideoType == VideoType.HdDvd;
|
||||||
@ -2440,7 +2439,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public ItemLookupInfo ParseName(string name)
|
public ItemLookupInfo ParseName(string name)
|
||||||
{
|
{
|
||||||
var resolver = new VideoResolver(GetNamingOptions(), new PatternsLogger());
|
var resolver = new VideoResolver(GetNamingOptions(), new NullLogger());
|
||||||
|
|
||||||
var result = resolver.CleanDateTime(name);
|
var result = resolver.CleanDateTime(name);
|
||||||
var cleanName = resolver.CleanString(result.Name);
|
var cleanName = resolver.CleanString(result.Name);
|
||||||
@ -2459,7 +2458,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
|
.SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var videoListResolver = new VideoListResolver(GetNamingOptions(), new PatternsLogger());
|
var videoListResolver = new VideoListResolver(GetNamingOptions(), new NullLogger());
|
||||||
|
|
||||||
var videos = videoListResolver.Resolve(fileSystemChildren);
|
var videos = videoListResolver.Resolve(fileSystemChildren);
|
||||||
|
|
||||||
@ -2505,7 +2504,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
|
.SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var videoListResolver = new VideoListResolver(GetNamingOptions(), new PatternsLogger());
|
var videoListResolver = new VideoListResolver(GetNamingOptions(), new NullLogger());
|
||||||
|
|
||||||
var videos = videoListResolver.Resolve(fileSystemChildren);
|
var videos = videoListResolver.Resolve(fileSystemChildren);
|
||||||
|
|
||||||
@ -2628,7 +2627,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
private void SetExtraTypeFromFilename(Video item)
|
private void SetExtraTypeFromFilename(Video item)
|
||||||
{
|
{
|
||||||
var resolver = new ExtraResolver(GetNamingOptions(), new PatternsLogger(), new RegexProvider());
|
var resolver = new ExtraResolver(GetNamingOptions(), new NullLogger(), new RegexProvider());
|
||||||
|
|
||||||
var result = resolver.GetExtraInfo(item.Path);
|
var result = resolver.GetExtraInfo(item.Path);
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ using MediaBrowser.Naming.Audio;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Emby.Server.Implementations.Logging;
|
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
@ -164,7 +163,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
{
|
{
|
||||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(libraryOptions);
|
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(libraryOptions);
|
||||||
|
|
||||||
var parser = new AlbumParser(namingOptions, new PatternsLogger());
|
var parser = new AlbumParser(namingOptions, new NullLogger());
|
||||||
var result = parser.ParseMultiPart(path);
|
var result = parser.ParseMultiPart(path);
|
||||||
|
|
||||||
return result.IsMultiPart;
|
return result.IsMultiPart;
|
||||||
|
@ -4,7 +4,7 @@ using MediaBrowser.Model.Entities;
|
|||||||
using MediaBrowser.Naming.Video;
|
using MediaBrowser.Naming.Video;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Emby.Server.Implementations.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library.Resolvers
|
namespace Emby.Server.Implementations.Library.Resolvers
|
||||||
{
|
{
|
||||||
@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
||||||
|
|
||||||
// If the path is a file check for a matching extensions
|
// If the path is a file check for a matching extensions
|
||||||
var parser = new MediaBrowser.Naming.Video.VideoResolver(namingOptions, new PatternsLogger());
|
var parser = new MediaBrowser.Naming.Video.VideoResolver(namingOptions, new NullLogger());
|
||||||
|
|
||||||
if (args.IsDirectory)
|
if (args.IsDirectory)
|
||||||
{
|
{
|
||||||
@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
{
|
{
|
||||||
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
||||||
|
|
||||||
var resolver = new Format3DParser(namingOptions, new PatternsLogger());
|
var resolver = new Format3DParser(namingOptions, new NullLogger());
|
||||||
var result = resolver.Parse(video.Path);
|
var result = resolver.Parse(video.Path);
|
||||||
|
|
||||||
Set3DFormat(video, result.Is3D, result.Format3D);
|
Set3DFormat(video, result.Is3D, result.Format3D);
|
||||||
|
@ -11,10 +11,10 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Server.Implementations.Logging;
|
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller.IO;
|
using MediaBrowser.Controller.IO;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
{
|
{
|
||||||
@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
|
|
||||||
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
||||||
|
|
||||||
var resolver = new VideoListResolver(namingOptions, new PatternsLogger());
|
var resolver = new VideoListResolver(namingOptions, new NullLogger());
|
||||||
var resolverResult = resolver.Resolve(files, suppportMultiEditions).ToList();
|
var resolverResult = resolver.Resolve(files, suppportMultiEditions).ToList();
|
||||||
|
|
||||||
var result = new MultiItemResolverResult
|
var result = new MultiItemResolverResult
|
||||||
@ -486,7 +486,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
}
|
}
|
||||||
|
|
||||||
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
||||||
var resolver = new StackResolver(namingOptions, new PatternsLogger());
|
var resolver = new StackResolver(namingOptions, new NullLogger());
|
||||||
|
|
||||||
var result = resolver.ResolveDirectories(folderPaths);
|
var result = resolver.ResolveDirectories(folderPaths);
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Server.Implementations.Logging;
|
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
@ -171,7 +170,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodeResolver = new MediaBrowser.Naming.TV.EpisodeResolver(namingOptions, new PatternsLogger());
|
var episodeResolver = new MediaBrowser.Naming.TV.EpisodeResolver(namingOptions, new NullLogger());
|
||||||
var episodeInfo = episodeResolver.Resolve(fullName, false, false);
|
var episodeInfo = episodeResolver.Resolve(fullName, false, false);
|
||||||
if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue)
|
if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
}
|
}
|
||||||
|
|
||||||
var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
|
var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
|
||||||
var reader = new XmlTvReader(path, GetLanguage(), null);
|
var reader = new XmlTvReader(path, GetLanguage());
|
||||||
|
|
||||||
var results = reader.GetProgrammes(channelNumber, startDateUtc, endDateUtc, cancellationToken);
|
var results = reader.GetProgrammes(channelNumber, startDateUtc, endDateUtc, cancellationToken);
|
||||||
return results.Select(p => GetProgramInfo(p, info));
|
return results.Select(p => GetProgramInfo(p, info));
|
||||||
@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
{
|
{
|
||||||
// Add the channel image url
|
// Add the channel image url
|
||||||
var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
|
var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
|
||||||
var reader = new XmlTvReader(path, GetLanguage(), null);
|
var reader = new XmlTvReader(path, GetLanguage());
|
||||||
var results = reader.GetChannels().ToList();
|
var results = reader.GetChannels().ToList();
|
||||||
|
|
||||||
if (channels != null)
|
if (channels != null)
|
||||||
@ -208,7 +208,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
{
|
{
|
||||||
// In theory this should never be called because there is always only one lineup
|
// In theory this should never be called because there is always only one lineup
|
||||||
var path = await GetXml(info.Path, CancellationToken.None).ConfigureAwait(false);
|
var path = await GetXml(info.Path, CancellationToken.None).ConfigureAwait(false);
|
||||||
var reader = new XmlTvReader(path, GetLanguage(), null);
|
var reader = new XmlTvReader(path, GetLanguage());
|
||||||
var results = reader.GetChannels();
|
var results = reader.GetChannels();
|
||||||
|
|
||||||
// Should this method be async?
|
// Should this method be async?
|
||||||
@ -219,7 +219,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
{
|
{
|
||||||
// In theory this should never be called because there is always only one lineup
|
// In theory this should never be called because there is always only one lineup
|
||||||
var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
|
var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
|
||||||
var reader = new XmlTvReader(path, GetLanguage(), null);
|
var reader = new XmlTvReader(path, GetLanguage());
|
||||||
var results = reader.GetChannels();
|
var results = reader.GetChannels();
|
||||||
|
|
||||||
// Should this method be async?
|
// Should this method be async?
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
using Patterns.Logging;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Logging
|
|
||||||
{
|
|
||||||
public class PatternsLogger : ILogger
|
|
||||||
{
|
|
||||||
private readonly MediaBrowser.Model.Logging.ILogger _logger;
|
|
||||||
|
|
||||||
public PatternsLogger()
|
|
||||||
: this(new MediaBrowser.Model.Logging.NullLogger())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatternsLogger(MediaBrowser.Model.Logging.ILogger logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Debug(string message, params object[] paramList)
|
|
||||||
{
|
|
||||||
_logger.Debug(message, paramList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Error(string message, params object[] paramList)
|
|
||||||
{
|
|
||||||
_logger.Error(message, paramList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ErrorException(string message, Exception exception, params object[] paramList)
|
|
||||||
{
|
|
||||||
_logger.ErrorException(message, exception, paramList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Fatal(string message, params object[] paramList)
|
|
||||||
{
|
|
||||||
_logger.Fatal(message, paramList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FatalException(string message, Exception exception, params object[] paramList)
|
|
||||||
{
|
|
||||||
_logger.FatalException(message, exception, paramList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Info(string message, params object[] paramList)
|
|
||||||
{
|
|
||||||
_logger.Info(message, paramList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Warn(string message, params object[] paramList)
|
|
||||||
{
|
|
||||||
_logger.Warn(message, paramList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Log(LogSeverity severity, string message, params object[] paramList)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LogMultiline(string message, LogSeverity severity, System.Text.StringBuilder additionalContent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="MediaBrowser.Naming" version="1.0.0.59" targetFramework="portable45-net45+win8" />
|
<package id="Emby.XmlTv" version="1.0.1" targetFramework="portable45-net45+win8" />
|
||||||
<package id="Patterns.Logging" version="1.0.0.6" targetFramework="portable45-net45+win8" />
|
<package id="MediaBrowser.Naming" version="1.0.2" targetFramework="portable45-net45+win8" />
|
||||||
<package id="UniversalDetector" version="1.0.1" targetFramework="portable45-net45+win8" />
|
<package id="UniversalDetector" version="1.0.1" targetFramework="portable45-net45+win8" />
|
||||||
<package id="Emby.XmlTv" version="1.0.63" targetFramework="portable45-net45+win8" />
|
|
||||||
</packages>
|
</packages>
|
@ -72,10 +72,6 @@
|
|||||||
<HintPath>..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll</HintPath>
|
<HintPath>..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Patterns.Logging, Version=1.0.6151.30227, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\Patterns.Logging.1.0.0.6\lib\portable-net45+win8\Patterns.Logging.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="MediaBrowser.IsoMounting.Linux">
|
<Reference Include="MediaBrowser.IsoMounting.Linux">
|
||||||
<HintPath>..\ThirdParty\MediaBrowser.IsoMounting.Linux\MediaBrowser.IsoMounting.Linux.dll</HintPath>
|
<HintPath>..\ThirdParty\MediaBrowser.IsoMounting.Linux\MediaBrowser.IsoMounting.Linux.dll</HintPath>
|
||||||
|
@ -10,18 +10,21 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Common.Implementations.EnvironmentInfo;
|
using Emby.Common.Implementations.EnvironmentInfo;
|
||||||
using Emby.Common.Implementations.IO;
|
using Emby.Common.Implementations.IO;
|
||||||
using Emby.Common.Implementations.Logging;
|
using Emby.Common.Implementations.Logging;
|
||||||
|
using Emby.Common.Implementations.Networking;
|
||||||
|
using Emby.Common.Implementations.Security;
|
||||||
using Emby.Server.Core;
|
using Emby.Server.Core;
|
||||||
using Emby.Server.Implementations.IO;
|
using Emby.Server.Implementations.IO;
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
|
using MediaBrowser.Server.Startup.Common.IO;
|
||||||
using Mono.Unix.Native;
|
using Mono.Unix.Native;
|
||||||
using NLog;
|
using NLog;
|
||||||
using ILogger = MediaBrowser.Model.Logging.ILogger;
|
using ILogger = MediaBrowser.Model.Logging.ILogger;
|
||||||
|
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Mono
|
namespace MediaBrowser.Server.Mono
|
||||||
{
|
{
|
||||||
@ -90,7 +93,22 @@ namespace MediaBrowser.Server.Mono
|
|||||||
|
|
||||||
var nativeApp = new MonoApp(options, logManager.GetLogger("App"), environmentInfo);
|
var nativeApp = new MonoApp(options, logManager.GetLogger("App"), environmentInfo);
|
||||||
|
|
||||||
_appHost = new ApplicationHost(appPaths, logManager, options, fileSystem, nativeApp, new PowerManagement(), "emby.mono.zip", environmentInfo);
|
var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths);
|
||||||
|
|
||||||
|
_appHost = new ApplicationHost(appPaths,
|
||||||
|
logManager,
|
||||||
|
options,
|
||||||
|
fileSystem,
|
||||||
|
nativeApp,
|
||||||
|
new PowerManagement(),
|
||||||
|
"emby.mono.zip",
|
||||||
|
environmentInfo,
|
||||||
|
imageEncoder,
|
||||||
|
new Startup.Common.SystemEvents(logManager.GetLogger("SystemEvents")),
|
||||||
|
new MemoryStreamProvider(),
|
||||||
|
new NetworkManager(logManager.GetLogger("NetworkManager")),
|
||||||
|
GenerateCertificate,
|
||||||
|
() => Environment.UserDomainName);
|
||||||
|
|
||||||
if (options.ContainsOption("-v"))
|
if (options.ContainsOption("-v"))
|
||||||
{
|
{
|
||||||
@ -115,6 +133,11 @@ namespace MediaBrowser.Server.Mono
|
|||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void GenerateCertificate(string certPath, string certHost)
|
||||||
|
{
|
||||||
|
CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, _logger);
|
||||||
|
}
|
||||||
|
|
||||||
private static MonoEnvironmentInfo GetEnvironmentInfo()
|
private static MonoEnvironmentInfo GetEnvironmentInfo()
|
||||||
{
|
{
|
||||||
var info = new MonoEnvironmentInfo();
|
var info = new MonoEnvironmentInfo();
|
||||||
|
@ -2,5 +2,4 @@
|
|||||||
<packages>
|
<packages>
|
||||||
<package id="Mono.Posix" version="4.0.0.0" targetFramework="net45" />
|
<package id="Mono.Posix" version="4.0.0.0" targetFramework="net45" />
|
||||||
<package id="NLog" version="4.4.0-betaV15" targetFramework="net46" />
|
<package id="NLog" version="4.4.0-betaV15" targetFramework="net46" />
|
||||||
<package id="Patterns.Logging" version="1.0.0.6" targetFramework="net46" />
|
|
||||||
</packages>
|
</packages>
|
@ -9,7 +9,7 @@ namespace Emby.Common.Implementations.Security
|
|||||||
{
|
{
|
||||||
private const string MonoTestRootAgency = "<RSAKeyValue><Modulus>v/4nALBxCE+9JgEC0LnDUvKh6e96PwTpN4Rj+vWnqKT7IAp1iK/JjuqvAg6DQ2vTfv0dTlqffmHH51OyioprcT5nzxcSTsZb/9jcHScG0s3/FRIWnXeLk/fgm7mSYhjUaHNI0m1/NTTktipicjKxo71hGIg9qucCWnDum+Krh/k=</Modulus><Exponent>AQAB</Exponent><P>9jbKxMXEruW2CfZrzhxtull4O8P47+mNsEL+9gf9QsRO1jJ77C+jmzfU6zbzjf8+ViK+q62tCMdC1ZzulwdpXQ==</P><Q>x5+p198l1PkK0Ga2mRh0SIYSykENpY2aLXoyZD/iUpKYAvATm0/wvKNrE4dKJyPCA+y3hfTdgVag+SP9avvDTQ==</Q><DP>ISSjCvXsUfbOGG05eddN1gXxL2pj+jegQRfjpk7RAsnWKvNExzhqd5x+ZuNQyc6QH5wxun54inP4RTUI0P/IaQ==</DP><DQ>R815VQmR3RIbPqzDXzv5j6CSH6fYlcTiQRtkBsUnzhWmkd/y3XmamO+a8zJFjOCCx9CcjpVuGziivBqi65lVPQ==</DQ><InverseQ>iYiu0KwMWI/dyqN3RJYUzuuLj02/oTD1pYpwo2rvNCXU1Q5VscOeu2DpNg1gWqI+1RrRCsEoaTNzXB1xtKNlSw==</InverseQ><D>nIfh1LYF8fjRBgMdAH/zt9UKHWiaCnc+jXzq5tkR8HVSKTVdzitD8bl1JgAfFQD8VjSXiCJqluexy/B5SGrCXQ49c78NIQj0hD+J13Y8/E0fUbW1QYbhj6Ff7oHyhaYe1WOQfkp2t/h+llHOdt1HRf7bt7dUknYp7m8bQKGxoYE=</D></RSAKeyValue>";
|
private const string MonoTestRootAgency = "<RSAKeyValue><Modulus>v/4nALBxCE+9JgEC0LnDUvKh6e96PwTpN4Rj+vWnqKT7IAp1iK/JjuqvAg6DQ2vTfv0dTlqffmHH51OyioprcT5nzxcSTsZb/9jcHScG0s3/FRIWnXeLk/fgm7mSYhjUaHNI0m1/NTTktipicjKxo71hGIg9qucCWnDum+Krh/k=</Modulus><Exponent>AQAB</Exponent><P>9jbKxMXEruW2CfZrzhxtull4O8P47+mNsEL+9gf9QsRO1jJ77C+jmzfU6zbzjf8+ViK+q62tCMdC1ZzulwdpXQ==</P><Q>x5+p198l1PkK0Ga2mRh0SIYSykENpY2aLXoyZD/iUpKYAvATm0/wvKNrE4dKJyPCA+y3hfTdgVag+SP9avvDTQ==</Q><DP>ISSjCvXsUfbOGG05eddN1gXxL2pj+jegQRfjpk7RAsnWKvNExzhqd5x+ZuNQyc6QH5wxun54inP4RTUI0P/IaQ==</DP><DQ>R815VQmR3RIbPqzDXzv5j6CSH6fYlcTiQRtkBsUnzhWmkd/y3XmamO+a8zJFjOCCx9CcjpVuGziivBqi65lVPQ==</DQ><InverseQ>iYiu0KwMWI/dyqN3RJYUzuuLj02/oTD1pYpwo2rvNCXU1Q5VscOeu2DpNg1gWqI+1RrRCsEoaTNzXB1xtKNlSw==</InverseQ><D>nIfh1LYF8fjRBgMdAH/zt9UKHWiaCnc+jXzq5tkR8HVSKTVdzitD8bl1JgAfFQD8VjSXiCJqluexy/B5SGrCXQ49c78NIQj0hD+J13Y8/E0fUbW1QYbhj6Ff7oHyhaYe1WOQfkp2t/h+llHOdt1HRf7bt7dUknYp7m8bQKGxoYE=</D></RSAKeyValue>";
|
||||||
|
|
||||||
internal static void CreateSelfSignCertificatePfx(
|
public static void CreateSelfSignCertificatePfx(
|
||||||
string fileName,
|
string fileName,
|
||||||
string hostname,
|
string hostname,
|
||||||
ILogger logger)
|
ILogger logger)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
using Emby.Drawing;
|
using System;
|
||||||
|
using Emby.Drawing;
|
||||||
using Emby.Drawing.Net;
|
using Emby.Drawing.Net;
|
||||||
using Emby.Drawing.ImageMagick;
|
using Emby.Drawing.ImageMagick;
|
||||||
using Emby.Server.Core;
|
using Emby.Server.Core;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
@ -16,14 +17,14 @@ namespace MediaBrowser.Server.Startup.Common
|
|||||||
ILogManager logManager,
|
ILogManager logManager,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
StartupOptions startupOptions,
|
StartupOptions startupOptions,
|
||||||
IHttpClient httpClient,
|
Func<IHttpClient> httpClient,
|
||||||
IServerConfigurationManager config)
|
IApplicationPaths appPaths)
|
||||||
{
|
{
|
||||||
if (!startupOptions.ContainsOption("-enablegdi"))
|
if (!startupOptions.ContainsOption("-enablegdi"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new ImageMagickEncoder(logManager.GetLogger("ImageMagick"), config.ApplicationPaths, httpClient, fileSystem, config);
|
return new ImageMagickEncoder(logManager.GetLogger("ImageMagick"), appPaths, httpClient, fileSystem);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -44,8 +44,8 @@
|
|||||||
<HintPath>..\packages\ini-parser.2.3.0\lib\net20\INIFileParser.dll</HintPath>
|
<HintPath>..\packages\ini-parser.2.3.0\lib\net20\INIFileParser.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MediaBrowser.Naming, Version=1.0.6151.30291, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="MediaBrowser.Naming, Version=1.0.6159.25070, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.59\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
|
<HintPath>..\packages\MediaBrowser.Naming.1.0.2\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Microsoft.IO.RecyclableMemoryStream, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
<Reference Include="Microsoft.IO.RecyclableMemoryStream, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
@ -60,14 +60,6 @@
|
|||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath>
|
<HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Patterns.Logging, Version=1.0.6159.22455, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\Patterns.Logging.1.0.0.7\lib\netstandard1.3\Patterns.Logging.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="ServiceStack, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="ServiceStack.Text, Version=4.5.4.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="ServiceStack.Text, Version=4.5.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\ServiceStack.Text.4.5.4\lib\net45\ServiceStack.Text.dll</HintPath>
|
<HintPath>..\packages\ServiceStack.Text.4.5.4\lib\net45\ServiceStack.Text.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
@ -76,10 +68,6 @@
|
|||||||
<HintPath>..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll</HintPath>
|
<HintPath>..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SocketHttpListener, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\SocketHttpListener.Portable.1.0.50\lib\portable-net45+win8\SocketHttpListener.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Configuration" />
|
<Reference Include="System.Configuration" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
@ -212,6 +200,10 @@
|
|||||||
<Project>{21002819-c39a-4d3e-be83-2a276a77fb1f}</Project>
|
<Project>{21002819-c39a-4d3e-be83-2a276a77fb1f}</Project>
|
||||||
<Name>RSSDP</Name>
|
<Name>RSSDP</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\ServiceStack\ServiceStack.csproj">
|
||||||
|
<Project>{680a1709-25eb-4d52-a87f-ee03ffd94baa}</Project>
|
||||||
|
<Name>ServiceStack</Name>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="ini-parser" version="2.3.0" targetFramework="net46" />
|
<package id="ini-parser" version="2.3.0" targetFramework="net46" />
|
||||||
<package id="MediaBrowser.Naming" version="1.0.0.59" targetFramework="net46" />
|
<package id="MediaBrowser.Naming" version="1.0.2" targetFramework="net46" />
|
||||||
<package id="Microsoft.IO.RecyclableMemoryStream" version="1.1.0.0" targetFramework="net46" />
|
<package id="Microsoft.IO.RecyclableMemoryStream" version="1.1.0.0" targetFramework="net46" />
|
||||||
<package id="Mono.Posix" version="4.0.0.0" targetFramework="net45" />
|
<package id="Mono.Posix" version="4.0.0.0" targetFramework="net45" />
|
||||||
<package id="Patterns.Logging" version="1.0.0.7" targetFramework="net46" />
|
|
||||||
<package id="ServiceStack.Text" version="4.5.4" targetFramework="net46" />
|
<package id="ServiceStack.Text" version="4.5.4" targetFramework="net46" />
|
||||||
<package id="SimpleInjector" version="3.2.4" targetFramework="net46" />
|
<package id="SimpleInjector" version="3.2.4" targetFramework="net46" />
|
||||||
<package id="SocketHttpListener.Portable" version="1.0.50" targetFramework="net46" />
|
|
||||||
</packages>
|
</packages>
|
@ -20,11 +20,14 @@ using System.Windows.Forms;
|
|||||||
using Emby.Common.Implementations.EnvironmentInfo;
|
using Emby.Common.Implementations.EnvironmentInfo;
|
||||||
using Emby.Common.Implementations.IO;
|
using Emby.Common.Implementations.IO;
|
||||||
using Emby.Common.Implementations.Logging;
|
using Emby.Common.Implementations.Logging;
|
||||||
|
using Emby.Common.Implementations.Networking;
|
||||||
|
using Emby.Common.Implementations.Security;
|
||||||
using Emby.Server.Core;
|
using Emby.Server.Core;
|
||||||
using Emby.Server.Core.Browser;
|
using Emby.Server.Core.Browser;
|
||||||
using Emby.Server.Implementations.IO;
|
using Emby.Server.Implementations.IO;
|
||||||
using ImageMagickSharp;
|
using ImageMagickSharp;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Server.Startup.Common.IO;
|
||||||
|
|
||||||
namespace MediaBrowser.ServerApplication
|
namespace MediaBrowser.ServerApplication
|
||||||
{
|
{
|
||||||
@ -64,8 +67,8 @@ namespace MediaBrowser.ServerApplication
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the entry point of the application.
|
/// Defines the entry point of the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Main()
|
public static void Main()
|
||||||
{
|
{
|
||||||
var options = new StartupOptions();
|
var options = new StartupOptions();
|
||||||
@ -319,14 +322,22 @@ namespace MediaBrowser.ServerApplication
|
|||||||
IsRunningAsService = runService
|
IsRunningAsService = runService
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths);
|
||||||
|
|
||||||
_appHost = new ApplicationHost(appPaths,
|
_appHost = new ApplicationHost(appPaths,
|
||||||
logManager,
|
logManager,
|
||||||
options,
|
options,
|
||||||
fileSystem,
|
fileSystem,
|
||||||
nativeApp,
|
nativeApp,
|
||||||
new PowerManagement(),
|
new PowerManagement(),
|
||||||
"emby.windows.zip",
|
"emby.windows.zip",
|
||||||
new EnvironmentInfo());
|
new EnvironmentInfo(),
|
||||||
|
imageEncoder,
|
||||||
|
new Server.Startup.Common.SystemEvents(logManager.GetLogger("SystemEvents")),
|
||||||
|
new RecyclableMemoryStreamProvider(),
|
||||||
|
new NetworkManager(logManager.GetLogger("NetworkManager")),
|
||||||
|
GenerateCertificate,
|
||||||
|
() => Environment.UserDomainName);
|
||||||
|
|
||||||
var initProgress = new Progress<double>();
|
var initProgress = new Progress<double>();
|
||||||
|
|
||||||
@ -367,6 +378,11 @@ namespace MediaBrowser.ServerApplication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void GenerateCertificate(string certPath, string certHost)
|
||||||
|
{
|
||||||
|
CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, _logger);
|
||||||
|
}
|
||||||
|
|
||||||
private static ServerNotifyIcon _serverNotifyIcon;
|
private static ServerNotifyIcon _serverNotifyIcon;
|
||||||
private static TaskScheduler _mainTaskScheduler;
|
private static TaskScheduler _mainTaskScheduler;
|
||||||
private static void ShowTrayIcon()
|
private static void ShowTrayIcon()
|
||||||
|
@ -78,10 +78,6 @@
|
|||||||
<HintPath>..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll</HintPath>
|
<HintPath>..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Patterns.Logging, Version=1.0.6151.30227, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\Patterns.Logging.1.0.0.6\lib\portable-net45+win8\Patterns.Logging.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Configuration" />
|
<Reference Include="System.Configuration" />
|
||||||
<Reference Include="System.Configuration.Install" />
|
<Reference Include="System.Configuration.Install" />
|
||||||
|
@ -2,6 +2,5 @@
|
|||||||
<packages>
|
<packages>
|
||||||
<package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" />
|
<package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" />
|
||||||
<package id="NLog" version="4.4.0-betaV15" targetFramework="net462" />
|
<package id="NLog" version="4.4.0-betaV15" targetFramework="net462" />
|
||||||
<package id="Patterns.Logging" version="1.0.0.6" targetFramework="net462" />
|
|
||||||
<package id="System.Data.SQLite.Core" version="1.0.103" targetFramework="net462" />
|
<package id="System.Data.SQLite.Core" version="1.0.103" targetFramework="net462" />
|
||||||
</packages>
|
</packages>
|
@ -1,5 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
|
||||||
<package id="WebMarkupMin.Core" version="2.1.0" targetFramework="net45" requireReinstallation="true" />
|
|
||||||
</packages>
|
</packages>
|
359
MediaBrowser.sln
359
MediaBrowser.sln
@ -78,6 +78,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.ImageMagick",
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.Net", "Emby.Drawing.Net\Emby.Drawing.Net.csproj", "{C97A239E-A96C-4D64-A844-CCF8CC30AECB}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.Net", "Emby.Drawing.Net\Emby.Drawing.Net.csproj", "{C97A239E-A96C-4D64-A844-CCF8CC30AECB}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack", "ServiceStack\ServiceStack.csproj", "{680A1709-25EB-4D52-A87F-EE03FFD94BAA}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocketHttpListener.Portable", "SocketHttpListener.Portable\SocketHttpListener.Portable.csproj", "{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -95,6 +99,11 @@ Global
|
|||||||
Release|Win32 = Release|Win32
|
Release|Win32 = Release|Win32
|
||||||
Release|x64 = Release|x64
|
Release|x64 = Release|x64
|
||||||
Release|x86 = Release|x86
|
Release|x86 = Release|x86
|
||||||
|
Signed|Any CPU = Signed|Any CPU
|
||||||
|
Signed|Mixed Platforms = Signed|Mixed Platforms
|
||||||
|
Signed|Win32 = Signed|Win32
|
||||||
|
Signed|x64 = Signed|x64
|
||||||
|
Signed|x86 = Signed|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
@ -120,6 +129,16 @@ Global
|
|||||||
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x64.ActiveCfg = Release|Any CPU
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.ActiveCfg = Release|Any CPU
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.Build.0 = Release|Any CPU
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|Any CPU.Build.0 = Release Mono|Any CPU
|
||||||
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|Mixed Platforms.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|Mixed Platforms.Build.0 = Release Mono|Any CPU
|
||||||
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|Win32.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|Win32.Build.0 = Release Mono|Any CPU
|
||||||
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|x64.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|x64.Build.0 = Release Mono|Any CPU
|
||||||
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|x86.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|x86.Build.0 = Release Mono|Any CPU
|
||||||
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -143,6 +162,16 @@ Global
|
|||||||
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x64.ActiveCfg = Release|Any CPU
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.ActiveCfg = Release|Any CPU
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.Build.0 = Release|Any CPU
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|Any CPU.Build.0 = Release Mono|Any CPU
|
||||||
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|Mixed Platforms.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|Mixed Platforms.Build.0 = Release Mono|Any CPU
|
||||||
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|Win32.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|Win32.Build.0 = Release Mono|Any CPU
|
||||||
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|x64.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|x64.Build.0 = Release Mono|Any CPU
|
||||||
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|x86.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|x86.Build.0 = Release Mono|Any CPU
|
||||||
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -166,6 +195,16 @@ Global
|
|||||||
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x64.ActiveCfg = Release|Any CPU
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.ActiveCfg = Release|Any CPU
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.Build.0 = Release|Any CPU
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|Any CPU.Build.0 = Release Mono|Any CPU
|
||||||
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|Mixed Platforms.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|Mixed Platforms.Build.0 = Release Mono|Any CPU
|
||||||
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|Win32.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|Win32.Build.0 = Release Mono|Any CPU
|
||||||
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|x64.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|x64.Build.0 = Release Mono|Any CPU
|
||||||
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|x86.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|x86.Build.0 = Release Mono|Any CPU
|
||||||
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -192,6 +231,16 @@ Global
|
|||||||
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x64.ActiveCfg = Release|Any CPU
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.ActiveCfg = Release|Any CPU
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.Build.0 = Release|Any CPU
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -215,6 +264,16 @@ Global
|
|||||||
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x64.ActiveCfg = Release|Any CPU
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.ActiveCfg = Release|Any CPU
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.Build.0 = Release|Any CPU
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|Any CPU.Build.0 = Release Mono|Any CPU
|
||||||
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|Mixed Platforms.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|Mixed Platforms.Build.0 = Release Mono|Any CPU
|
||||||
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|Win32.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|Win32.Build.0 = Release Mono|Any CPU
|
||||||
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|x64.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|x64.Build.0 = Release Mono|Any CPU
|
||||||
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|x86.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|x86.Build.0 = Release Mono|Any CPU
|
||||||
{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -236,6 +295,16 @@ Global
|
|||||||
{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Win32.ActiveCfg = Release|Any CPU
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x64.ActiveCfg = Release|Any CPU
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x86.ActiveCfg = Release|Any CPU
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Signed|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Signed|Any CPU.Build.0 = Release Mono|Any CPU
|
||||||
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Signed|Mixed Platforms.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Signed|Mixed Platforms.Build.0 = Release Mono|Any CPU
|
||||||
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Signed|Win32.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Signed|Win32.Build.0 = Release Mono|Any CPU
|
||||||
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Signed|x64.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Signed|x64.Build.0 = Release Mono|Any CPU
|
||||||
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Signed|x86.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{2E781478-814D-4A48-9D80-BFF206441A65}.Signed|x86.Build.0 = Release Mono|Any CPU
|
||||||
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -257,6 +326,16 @@ Global
|
|||||||
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Win32.ActiveCfg = Release|Any CPU
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|x64.ActiveCfg = Release|Any CPU
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|x86.ActiveCfg = Release|Any CPU
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -278,6 +357,16 @@ Global
|
|||||||
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Win32.ActiveCfg = Release|Any CPU
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x64.ActiveCfg = Release|Any CPU
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x86.ActiveCfg = Release|Any CPU
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|Any CPU.Build.0 = Release Mono|Any CPU
|
||||||
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|Mixed Platforms.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|Mixed Platforms.Build.0 = Release Mono|Any CPU
|
||||||
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|Win32.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|Win32.Build.0 = Release Mono|Any CPU
|
||||||
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|x64.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|x64.Build.0 = Release Mono|Any CPU
|
||||||
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|x86.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|x86.Build.0 = Release Mono|Any CPU
|
||||||
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -300,6 +389,16 @@ Global
|
|||||||
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Win32.ActiveCfg = Release|Any CPU
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|x64.ActiveCfg = Release|Any CPU
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|x86.ActiveCfg = Release|Any CPU
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|Mixed Platforms.ActiveCfg = Debug|x86
|
||||||
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|Mixed Platforms.Build.0 = Debug|x86
|
||||||
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|Win32.ActiveCfg = Debug|x86
|
||||||
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|Win32.Build.0 = Debug|x86
|
||||||
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|x86.ActiveCfg = Debug|x86
|
||||||
|
{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|x86.Build.0 = Debug|x86
|
||||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -321,6 +420,16 @@ Global
|
|||||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Win32.ActiveCfg = Release|Any CPU
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|x64.ActiveCfg = Release|Any CPU
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|x86.ActiveCfg = Release|Any CPU
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Any CPU.Build.0 = Release Mono|Any CPU
|
||||||
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Mixed Platforms.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Mixed Platforms.Build.0 = Release Mono|Any CPU
|
||||||
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Win32.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Win32.Build.0 = Release Mono|Any CPU
|
||||||
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|x64.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|x64.Build.0 = Release Mono|Any CPU
|
||||||
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|x86.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|x86.Build.0 = Release Mono|Any CPU
|
||||||
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -342,6 +451,16 @@ Global
|
|||||||
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Win32.ActiveCfg = Release|Any CPU
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|x64.ActiveCfg = Release|Any CPU
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|x86.ActiveCfg = Release|Any CPU
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|Any CPU.Build.0 = Release Mono|Any CPU
|
||||||
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|Mixed Platforms.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|Mixed Platforms.Build.0 = Release Mono|Any CPU
|
||||||
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|Win32.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|Win32.Build.0 = Release Mono|Any CPU
|
||||||
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|x64.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|x64.Build.0 = Release Mono|Any CPU
|
||||||
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|x86.ActiveCfg = Release Mono|Any CPU
|
||||||
|
{4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|x86.Build.0 = Release Mono|Any CPU
|
||||||
{23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{23499896-B135-4527-8574-C26E926EA99E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{23499896-B135-4527-8574-C26E926EA99E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -363,6 +482,16 @@ Global
|
|||||||
{23499896-B135-4527-8574-C26E926EA99E}.Release|Win32.ActiveCfg = Release|Any CPU
|
{23499896-B135-4527-8574-C26E926EA99E}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
{23499896-B135-4527-8574-C26E926EA99E}.Release|x64.ActiveCfg = Release|Any CPU
|
{23499896-B135-4527-8574-C26E926EA99E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{23499896-B135-4527-8574-C26E926EA99E}.Release|x86.ActiveCfg = Release|Any CPU
|
{23499896-B135-4527-8574-C26E926EA99E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{23499896-B135-4527-8574-C26E926EA99E}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{23499896-B135-4527-8574-C26E926EA99E}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{23499896-B135-4527-8574-C26E926EA99E}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{23499896-B135-4527-8574-C26E926EA99E}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{23499896-B135-4527-8574-C26E926EA99E}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{23499896-B135-4527-8574-C26E926EA99E}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{23499896-B135-4527-8574-C26E926EA99E}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{23499896-B135-4527-8574-C26E926EA99E}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{23499896-B135-4527-8574-C26E926EA99E}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{23499896-B135-4527-8574-C26E926EA99E}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -384,6 +513,16 @@ Global
|
|||||||
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Win32.ActiveCfg = Release|Any CPU
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|x64.ActiveCfg = Release|Any CPU
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|x86.ActiveCfg = Release|Any CPU
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{175A9388-F352-4586-A6B4-070DED62B644}.Debug|Any CPU.ActiveCfg = Debug|x86
|
{175A9388-F352-4586-A6B4-070DED62B644}.Debug|Any CPU.ActiveCfg = Debug|x86
|
||||||
{175A9388-F352-4586-A6B4-070DED62B644}.Debug|Any CPU.Build.0 = Debug|x86
|
{175A9388-F352-4586-A6B4-070DED62B644}.Debug|Any CPU.Build.0 = Debug|x86
|
||||||
{175A9388-F352-4586-A6B4-070DED62B644}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
|
{175A9388-F352-4586-A6B4-070DED62B644}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
|
||||||
@ -410,6 +549,16 @@ Global
|
|||||||
{175A9388-F352-4586-A6B4-070DED62B644}.Release|x64.ActiveCfg = Release|Any CPU
|
{175A9388-F352-4586-A6B4-070DED62B644}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{175A9388-F352-4586-A6B4-070DED62B644}.Release|x86.ActiveCfg = Release|x86
|
{175A9388-F352-4586-A6B4-070DED62B644}.Release|x86.ActiveCfg = Release|x86
|
||||||
{175A9388-F352-4586-A6B4-070DED62B644}.Release|x86.Build.0 = Release|x86
|
{175A9388-F352-4586-A6B4-070DED62B644}.Release|x86.Build.0 = Release|x86
|
||||||
|
{175A9388-F352-4586-A6B4-070DED62B644}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{175A9388-F352-4586-A6B4-070DED62B644}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{175A9388-F352-4586-A6B4-070DED62B644}.Signed|Mixed Platforms.ActiveCfg = Release|x86
|
||||||
|
{175A9388-F352-4586-A6B4-070DED62B644}.Signed|Mixed Platforms.Build.0 = Release|x86
|
||||||
|
{175A9388-F352-4586-A6B4-070DED62B644}.Signed|Win32.ActiveCfg = Release|x86
|
||||||
|
{175A9388-F352-4586-A6B4-070DED62B644}.Signed|Win32.Build.0 = Release|x86
|
||||||
|
{175A9388-F352-4586-A6B4-070DED62B644}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{175A9388-F352-4586-A6B4-070DED62B644}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{175A9388-F352-4586-A6B4-070DED62B644}.Signed|x86.ActiveCfg = Release|x86
|
||||||
|
{175A9388-F352-4586-A6B4-070DED62B644}.Signed|x86.Build.0 = Release|x86
|
||||||
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -431,6 +580,16 @@ Global
|
|||||||
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Release|Win32.ActiveCfg = Release|Any CPU
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Release|x64.ActiveCfg = Release|Any CPU
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Release|x86.ActiveCfg = Release|Any CPU
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{B90AB8F2-1BFF-4568-A3FD-2A338A435A75}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -452,6 +611,16 @@ Global
|
|||||||
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Win32.ActiveCfg = Release|Any CPU
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x64.ActiveCfg = Release|Any CPU
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.ActiveCfg = Release|Any CPU
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -482,6 +651,16 @@ Global
|
|||||||
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|x64.Build.0 = Release|Any CPU
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|x86.ActiveCfg = Release|Any CPU
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|x86.Build.0 = Release|Any CPU
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -512,6 +691,16 @@ Global
|
|||||||
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|x64.Build.0 = Release|Any CPU
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|x86.ActiveCfg = Release|Any CPU
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|x86.Build.0 = Release|Any CPU
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{5A27010A-09C6-4E86-93EA-437484C10917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{5A27010A-09C6-4E86-93EA-437484C10917}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5A27010A-09C6-4E86-93EA-437484C10917}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -542,6 +731,16 @@ Global
|
|||||||
{5A27010A-09C6-4E86-93EA-437484C10917}.Release|x64.Build.0 = Release|Any CPU
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{5A27010A-09C6-4E86-93EA-437484C10917}.Release|x86.ActiveCfg = Release|Any CPU
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{5A27010A-09C6-4E86-93EA-437484C10917}.Release|x86.Build.0 = Release|Any CPU
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{5A27010A-09C6-4E86-93EA-437484C10917}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -572,6 +771,16 @@ Global
|
|||||||
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x64.Build.0 = Release|Any CPU
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x86.ActiveCfg = Release|Any CPU
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x86.Build.0 = Release|Any CPU
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -602,6 +811,16 @@ Global
|
|||||||
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|x64.Build.0 = Release|Any CPU
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|x86.ActiveCfg = Release|Any CPU
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|x86.Build.0 = Release|Any CPU
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{E383961B-9356-4D5D-8233-9A1079D03055}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -632,6 +851,16 @@ Global
|
|||||||
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x64.Build.0 = Release|Any CPU
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x86.ActiveCfg = Release|Any CPU
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x86.Build.0 = Release|Any CPU
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -662,6 +891,16 @@ Global
|
|||||||
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x64.Build.0 = Release|Any CPU
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x86.ActiveCfg = Release|Any CPU
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x86.Build.0 = Release|Any CPU
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -692,6 +931,16 @@ Global
|
|||||||
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x64.Build.0 = Release|Any CPU
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x86.ActiveCfg = Release|Any CPU
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x86.Build.0 = Release|Any CPU
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{65AA7D67-8059-40CD-91F1-16D02687226C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{65AA7D67-8059-40CD-91F1-16D02687226C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{65AA7D67-8059-40CD-91F1-16D02687226C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -722,6 +971,16 @@ Global
|
|||||||
{65AA7D67-8059-40CD-91F1-16D02687226C}.Release|x64.Build.0 = Release|Any CPU
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{65AA7D67-8059-40CD-91F1-16D02687226C}.Release|x86.ActiveCfg = Release|Any CPU
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{65AA7D67-8059-40CD-91F1-16D02687226C}.Release|x86.Build.0 = Release|Any CPU
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -752,6 +1011,16 @@ Global
|
|||||||
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Release|x64.Build.0 = Release|Any CPU
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Release|x86.ActiveCfg = Release|Any CPU
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Release|x86.Build.0 = Release|Any CPU
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{6CFEE013-6E7C-432B-AC37-CABF0880C69A}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
@ -782,6 +1051,96 @@ Global
|
|||||||
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Release|x64.Build.0 = Release|Any CPU
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Release|x86.ActiveCfg = Release|Any CPU
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Release|x86.Build.0 = Release|Any CPU
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C97A239E-A96C-4D64-A844-CCF8CC30AECB}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|Win32.ActiveCfg = Debug|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|Win32.Build.0 = Debug|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|Win32.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|x64.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|x86.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|Win32.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|Any CPU.ActiveCfg = Signed|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|Any CPU.Build.0 = Signed|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|Mixed Platforms.ActiveCfg = Signed|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|Mixed Platforms.Build.0 = Signed|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|Win32.ActiveCfg = Signed|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|Win32.Build.0 = Signed|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|x64.ActiveCfg = Signed|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|x64.Build.0 = Signed|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|x86.ActiveCfg = Signed|Any CPU
|
||||||
|
{680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|x86.Build.0 = Signed|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|Win32.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|Win32.Build.0 = Debug|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|Win32.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|x64.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|x86.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|Win32.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|Win32.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|Win32.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|x64.Build.0 = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
27
ServiceStack/FilterAttributeCache.cs
Normal file
27
ServiceStack/FilterAttributeCache.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using ServiceStack;
|
||||||
|
|
||||||
|
namespace ServiceStack.Support.WebHost
|
||||||
|
{
|
||||||
|
public static class FilterAttributeCache
|
||||||
|
{
|
||||||
|
public static MediaBrowser.Model.Services.IHasRequestFilter[] GetRequestFilterAttributes(Type requestDtoType)
|
||||||
|
{
|
||||||
|
var attributes = requestDtoType.AllAttributes().OfType<MediaBrowser.Model.Services.IHasRequestFilter>().ToList();
|
||||||
|
|
||||||
|
var serviceType = ServiceStackHost.Instance.Metadata.GetServiceTypeByRequest(requestDtoType);
|
||||||
|
if (serviceType != null)
|
||||||
|
{
|
||||||
|
attributes.AddRange(serviceType.AllAttributes().OfType<MediaBrowser.Model.Services.IHasRequestFilter>());
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes.Sort((x,y) => x.Priority - y.Priority);
|
||||||
|
|
||||||
|
return attributes.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
ServiceStack/Host/ActionContext.cs
Normal file
27
ServiceStack/Host/ActionContext.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ServiceStack.Host
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Context to capture IService action
|
||||||
|
/// </summary>
|
||||||
|
public class ActionContext
|
||||||
|
{
|
||||||
|
public const string AnyAction = "ANY";
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public ActionInvokerFn ServiceAction { get; set; }
|
||||||
|
public MediaBrowser.Model.Services.IHasRequestFilter[] RequestFilters { get; set; }
|
||||||
|
|
||||||
|
public static string Key(Type serviceType, string method, string requestDtoName)
|
||||||
|
{
|
||||||
|
return serviceType.FullName + " " + method.ToUpper() + " " + requestDtoName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AnyKey(Type serviceType, string requestDtoName)
|
||||||
|
{
|
||||||
|
return Key(serviceType, AnyAction, requestDtoName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
ServiceStack/Host/ContentTypes.cs
Normal file
77
ServiceStack/Host/ContentTypes.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
|
||||||
|
namespace ServiceStack.Host
|
||||||
|
{
|
||||||
|
public class ContentTypes
|
||||||
|
{
|
||||||
|
public static ContentTypes Instance = new ContentTypes();
|
||||||
|
|
||||||
|
public void SerializeToStream(IRequest req, object response, Stream responseStream)
|
||||||
|
{
|
||||||
|
var contentType = req.ResponseContentType;
|
||||||
|
var serializer = GetResponseSerializer(contentType);
|
||||||
|
if (serializer == null)
|
||||||
|
throw new NotSupportedException("ContentType not supported: " + contentType);
|
||||||
|
|
||||||
|
var httpRes = new HttpResponseStreamWrapper(responseStream, req)
|
||||||
|
{
|
||||||
|
Dto = req.Response.Dto
|
||||||
|
};
|
||||||
|
serializer(req, response, httpRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action<IRequest, object, IResponse> GetResponseSerializer(string contentType)
|
||||||
|
{
|
||||||
|
var serializer = GetStreamSerializer(contentType);
|
||||||
|
if (serializer == null) return null;
|
||||||
|
|
||||||
|
return (httpReq, dto, httpRes) => serializer(httpReq, dto, httpRes.OutputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action<IRequest, object, Stream> GetStreamSerializer(string contentType)
|
||||||
|
{
|
||||||
|
switch (GetRealContentType(contentType))
|
||||||
|
{
|
||||||
|
case "application/xml":
|
||||||
|
case "text/xml":
|
||||||
|
case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
|
||||||
|
return (r, o, s) => ServiceStackHost.Instance.SerializeToXml(o, s);
|
||||||
|
|
||||||
|
case "application/json":
|
||||||
|
case "text/json":
|
||||||
|
return (r, o, s) => ServiceStackHost.Instance.SerializeToJson(o, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Func<Type, Stream, object> GetStreamDeserializer(string contentType)
|
||||||
|
{
|
||||||
|
switch (GetRealContentType(contentType))
|
||||||
|
{
|
||||||
|
case "application/xml":
|
||||||
|
case "text/xml":
|
||||||
|
case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
|
||||||
|
return ServiceStackHost.Instance.DeserializeXml;
|
||||||
|
|
||||||
|
case "application/json":
|
||||||
|
case "text/json":
|
||||||
|
return ServiceStackHost.Instance.DeserializeJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRealContentType(string contentType)
|
||||||
|
{
|
||||||
|
return contentType == null
|
||||||
|
? null
|
||||||
|
: contentType.Split(';')[0].ToLower().Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
95
ServiceStack/Host/HttpResponseStreamWrapper.cs
Normal file
95
ServiceStack/Host/HttpResponseStreamWrapper.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
|
||||||
|
namespace ServiceStack.Host
|
||||||
|
{
|
||||||
|
public class HttpResponseStreamWrapper : IHttpResponse
|
||||||
|
{
|
||||||
|
private static readonly UTF8Encoding UTF8EncodingWithoutBom = new UTF8Encoding(false);
|
||||||
|
|
||||||
|
public HttpResponseStreamWrapper(Stream stream, IRequest request)
|
||||||
|
{
|
||||||
|
this.OutputStream = stream;
|
||||||
|
this.Request = request;
|
||||||
|
this.Headers = new Dictionary<string, string>();
|
||||||
|
this.Items = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, string> Headers { get; set; }
|
||||||
|
|
||||||
|
public object OriginalResponse
|
||||||
|
{
|
||||||
|
get { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRequest Request { get; private set; }
|
||||||
|
|
||||||
|
public int StatusCode { set; get; }
|
||||||
|
public string StatusDescription { set; get; }
|
||||||
|
public string ContentType { get; set; }
|
||||||
|
|
||||||
|
public void AddHeader(string name, string value)
|
||||||
|
{
|
||||||
|
this.Headers[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetHeader(string name)
|
||||||
|
{
|
||||||
|
return this.Headers[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Redirect(string url)
|
||||||
|
{
|
||||||
|
this.Headers["Location"] = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream OutputStream { get; private set; }
|
||||||
|
|
||||||
|
public object Dto { get; set; }
|
||||||
|
|
||||||
|
public void Write(string text)
|
||||||
|
{
|
||||||
|
var bytes = UTF8EncodingWithoutBom.GetBytes(text);
|
||||||
|
OutputStream.Write(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UseBufferedStream { get; set; }
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
if (IsClosed) return;
|
||||||
|
|
||||||
|
OutputStream.Dispose();
|
||||||
|
IsClosed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void End()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Flush()
|
||||||
|
{
|
||||||
|
OutputStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsClosed { get; private set; }
|
||||||
|
|
||||||
|
public void SetContentLength(long contentLength) {}
|
||||||
|
|
||||||
|
public bool KeepAlive { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, object> Items { get; private set; }
|
||||||
|
|
||||||
|
public void SetCookie(Cookie cookie)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearCookies()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
200
ServiceStack/Host/RestHandler.cs
Normal file
200
ServiceStack/Host/RestHandler.cs
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
|
||||||
|
namespace ServiceStack.Host
|
||||||
|
{
|
||||||
|
public class RestHandler
|
||||||
|
{
|
||||||
|
public string RequestName { get; set; }
|
||||||
|
|
||||||
|
public async Task<object> HandleResponseAsync(object response)
|
||||||
|
{
|
||||||
|
var taskResponse = response as Task;
|
||||||
|
|
||||||
|
if (taskResponse == null)
|
||||||
|
{
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
await taskResponse.ConfigureAwait(false);
|
||||||
|
|
||||||
|
var taskResult = ServiceStackHost.Instance.GetTaskResult(taskResponse, RequestName);
|
||||||
|
|
||||||
|
var taskResults = taskResult as Task[];
|
||||||
|
|
||||||
|
if (taskResults == null)
|
||||||
|
{
|
||||||
|
var subTask = taskResult as Task;
|
||||||
|
if (subTask != null)
|
||||||
|
taskResult = ServiceStackHost.Instance.GetTaskResult(subTask, RequestName);
|
||||||
|
|
||||||
|
return taskResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskResults.Length == 0)
|
||||||
|
{
|
||||||
|
return new object[] { };
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstResponse = ServiceStackHost.Instance.GetTaskResult(taskResults[0], RequestName);
|
||||||
|
var batchedResponses = firstResponse != null
|
||||||
|
? (object[])Array.CreateInstance(firstResponse.GetType(), taskResults.Length)
|
||||||
|
: new object[taskResults.Length];
|
||||||
|
batchedResponses[0] = firstResponse;
|
||||||
|
for (var i = 1; i < taskResults.Length; i++)
|
||||||
|
{
|
||||||
|
batchedResponses[i] = ServiceStackHost.Instance.GetTaskResult(taskResults[i], RequestName);
|
||||||
|
}
|
||||||
|
return batchedResponses;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static object CreateContentTypeRequest(IRequest httpReq, Type requestType, string contentType)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(contentType) && httpReq.ContentLength > 0)
|
||||||
|
{
|
||||||
|
var deserializer = ContentTypes.Instance.GetStreamDeserializer(contentType);
|
||||||
|
if (deserializer != null)
|
||||||
|
{
|
||||||
|
return deserializer(requestType, httpReq.InputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ServiceStackHost.Instance.CreateInstance(requestType); //Return an empty DTO, even for empty request bodies
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static object GetCustomRequestFromBinder(IRequest httpReq, Type requestType)
|
||||||
|
{
|
||||||
|
Func<IRequest, object> requestFactoryFn;
|
||||||
|
ServiceStackHost.Instance.ServiceController.RequestTypeFactoryMap.TryGetValue(
|
||||||
|
requestType, out requestFactoryFn);
|
||||||
|
|
||||||
|
return requestFactoryFn != null ? requestFactoryFn(httpReq) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RestPath FindMatchingRestPath(string httpMethod, string pathInfo, out string contentType)
|
||||||
|
{
|
||||||
|
pathInfo = GetSanitizedPathInfo(pathInfo, out contentType);
|
||||||
|
|
||||||
|
return ServiceStackHost.Instance.ServiceController.GetRestPathForRequest(httpMethod, pathInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetSanitizedPathInfo(string pathInfo, out string contentType)
|
||||||
|
{
|
||||||
|
contentType = null;
|
||||||
|
var pos = pathInfo.LastIndexOf('.');
|
||||||
|
if (pos >= 0)
|
||||||
|
{
|
||||||
|
var format = pathInfo.Substring(pos + 1);
|
||||||
|
contentType = GetFormatContentType(format);
|
||||||
|
if (contentType != null)
|
||||||
|
{
|
||||||
|
pathInfo = pathInfo.Substring(0, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pathInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetFormatContentType(string format)
|
||||||
|
{
|
||||||
|
//built-in formats
|
||||||
|
if (format == "json")
|
||||||
|
return "application/json";
|
||||||
|
if (format == "xml")
|
||||||
|
return "application/xml";
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestPath GetRestPath(string httpMethod, string pathInfo)
|
||||||
|
{
|
||||||
|
if (this.RestPath == null)
|
||||||
|
{
|
||||||
|
string contentType;
|
||||||
|
this.RestPath = FindMatchingRestPath(httpMethod, pathInfo, out contentType);
|
||||||
|
|
||||||
|
if (contentType != null)
|
||||||
|
ResponseContentType = contentType;
|
||||||
|
}
|
||||||
|
return this.RestPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestPath RestPath { get; set; }
|
||||||
|
|
||||||
|
// Set from SSHHF.GetHandlerForPathInfo()
|
||||||
|
public string ResponseContentType { get; set; }
|
||||||
|
|
||||||
|
public async Task ProcessRequestAsync(IRequest httpReq, IResponse httpRes, string operationName)
|
||||||
|
{
|
||||||
|
var appHost = ServiceStackHost.Instance;
|
||||||
|
|
||||||
|
var restPath = GetRestPath(httpReq.Verb, httpReq.PathInfo);
|
||||||
|
if (restPath == null)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("No RestPath found for: " + httpReq.Verb + " " + httpReq.PathInfo);
|
||||||
|
}
|
||||||
|
httpReq.SetRoute(restPath);
|
||||||
|
|
||||||
|
if (ResponseContentType != null)
|
||||||
|
httpReq.ResponseContentType = ResponseContentType;
|
||||||
|
|
||||||
|
var request = httpReq.Dto = CreateRequest(httpReq, restPath);
|
||||||
|
|
||||||
|
if (appHost.ApplyRequestFilters(httpReq, httpRes, request))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var rawResponse = await ServiceStackHost.Instance.ServiceController.Execute(request, httpReq).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (httpRes.IsClosed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var response = await HandleResponseAsync(rawResponse).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (appHost.ApplyResponseFilters(httpReq, httpRes, response))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await httpRes.WriteToResponse(httpReq, response).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object CreateRequest(IRequest httpReq, RestPath restPath)
|
||||||
|
{
|
||||||
|
var dtoFromBinder = GetCustomRequestFromBinder(httpReq, restPath.RequestType);
|
||||||
|
if (dtoFromBinder != null)
|
||||||
|
return dtoFromBinder;
|
||||||
|
|
||||||
|
var requestParams = httpReq.GetFlattenedRequestParams();
|
||||||
|
return CreateRequest(httpReq, restPath, requestParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams)
|
||||||
|
{
|
||||||
|
var requestDto = CreateContentTypeRequest(httpReq, restPath.RequestType, httpReq.ContentType);
|
||||||
|
|
||||||
|
return CreateRequest(httpReq, restPath, requestParams, requestDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams, object requestDto)
|
||||||
|
{
|
||||||
|
string contentType;
|
||||||
|
var pathInfo = !restPath.IsWildCardPath
|
||||||
|
? GetSanitizedPathInfo(httpReq.PathInfo, out contentType)
|
||||||
|
: httpReq.PathInfo;
|
||||||
|
|
||||||
|
return restPath.CreateRequest(pathInfo, requestParams, requestDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used in Unit tests
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object CreateRequest(IRequest httpReq, string operationName)
|
||||||
|
{
|
||||||
|
if (this.RestPath == null)
|
||||||
|
throw new ArgumentNullException("No RestPath found");
|
||||||
|
|
||||||
|
return CreateRequest(httpReq, this.RestPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
443
ServiceStack/Host/RestPath.cs
Normal file
443
ServiceStack/Host/RestPath.cs
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using ServiceStack.Serialization;
|
||||||
|
|
||||||
|
namespace ServiceStack.Host
|
||||||
|
{
|
||||||
|
public class RestPath
|
||||||
|
{
|
||||||
|
private const string WildCard = "*";
|
||||||
|
private const char WildCardChar = '*';
|
||||||
|
private const string PathSeperator = "/";
|
||||||
|
private const char PathSeperatorChar = '/';
|
||||||
|
private const char ComponentSeperator = '.';
|
||||||
|
private const string VariablePrefix = "{";
|
||||||
|
|
||||||
|
readonly bool[] componentsWithSeparators;
|
||||||
|
|
||||||
|
private readonly string restPath;
|
||||||
|
private readonly string allowedVerbs;
|
||||||
|
private readonly bool allowsAllVerbs;
|
||||||
|
public bool IsWildCardPath { get; private set; }
|
||||||
|
|
||||||
|
private readonly string[] literalsToMatch;
|
||||||
|
|
||||||
|
private readonly string[] variablesNames;
|
||||||
|
|
||||||
|
private readonly bool[] isWildcard;
|
||||||
|
private readonly int wildcardCount = 0;
|
||||||
|
|
||||||
|
public int VariableArgsCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of segments separated by '/' determinable by path.Split('/').Length
|
||||||
|
/// e.g. /path/to/here.ext == 3
|
||||||
|
/// </summary>
|
||||||
|
public int PathComponentsCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total number of segments after subparts have been exploded ('.')
|
||||||
|
/// e.g. /path/to/here.ext == 4
|
||||||
|
/// </summary>
|
||||||
|
public int TotalComponentsCount { get; set; }
|
||||||
|
|
||||||
|
public string[] Verbs
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return allowsAllVerbs
|
||||||
|
? new[] { ActionContext.AnyAction }
|
||||||
|
: AllowedVerbs.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type RequestType { get; private set; }
|
||||||
|
|
||||||
|
public string Path { get { return this.restPath; } }
|
||||||
|
|
||||||
|
public string Summary { get; private set; }
|
||||||
|
|
||||||
|
public string Notes { get; private set; }
|
||||||
|
|
||||||
|
public bool AllowsAllVerbs { get { return this.allowsAllVerbs; } }
|
||||||
|
|
||||||
|
public string AllowedVerbs { get { return this.allowedVerbs; } }
|
||||||
|
|
||||||
|
public int Priority { get; set; } //passed back to RouteAttribute
|
||||||
|
|
||||||
|
public static string[] GetPathPartsForMatching(string pathInfo)
|
||||||
|
{
|
||||||
|
var parts = pathInfo.ToLower().Split(PathSeperatorChar)
|
||||||
|
.Where(x => !string.IsNullOrEmpty(x)).ToArray();
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> GetFirstMatchHashKeys(string[] pathPartsForMatching)
|
||||||
|
{
|
||||||
|
var hashPrefix = pathPartsForMatching.Length + PathSeperator;
|
||||||
|
return GetPotentialMatchesWithPrefix(hashPrefix, pathPartsForMatching);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> GetFirstMatchWildCardHashKeys(string[] pathPartsForMatching)
|
||||||
|
{
|
||||||
|
const string hashPrefix = WildCard + PathSeperator;
|
||||||
|
return GetPotentialMatchesWithPrefix(hashPrefix, pathPartsForMatching);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetPotentialMatchesWithPrefix(string hashPrefix, string[] pathPartsForMatching)
|
||||||
|
{
|
||||||
|
foreach (var part in pathPartsForMatching)
|
||||||
|
{
|
||||||
|
yield return hashPrefix + part;
|
||||||
|
var subParts = part.Split(ComponentSeperator);
|
||||||
|
if (subParts.Length == 1) continue;
|
||||||
|
|
||||||
|
foreach (var subPart in subParts)
|
||||||
|
{
|
||||||
|
yield return hashPrefix + subPart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestPath(Type requestType, string path, string verbs, string summary = null, string notes = null)
|
||||||
|
{
|
||||||
|
this.RequestType = requestType;
|
||||||
|
this.Summary = summary;
|
||||||
|
this.Notes = notes;
|
||||||
|
this.restPath = path;
|
||||||
|
|
||||||
|
this.allowsAllVerbs = verbs == null || verbs == WildCard;
|
||||||
|
if (!this.allowsAllVerbs)
|
||||||
|
{
|
||||||
|
this.allowedVerbs = verbs.ToUpper();
|
||||||
|
}
|
||||||
|
|
||||||
|
var componentsList = new List<string>();
|
||||||
|
|
||||||
|
//We only split on '.' if the restPath has them. Allows for /{action}.{type}
|
||||||
|
var hasSeparators = new List<bool>();
|
||||||
|
foreach (var component in this.restPath.Split(PathSeperatorChar))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(component)) continue;
|
||||||
|
|
||||||
|
if (component.Contains(VariablePrefix)
|
||||||
|
&& component.IndexOf(ComponentSeperator) != -1)
|
||||||
|
{
|
||||||
|
hasSeparators.Add(true);
|
||||||
|
componentsList.AddRange(component.Split(ComponentSeperator));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasSeparators.Add(false);
|
||||||
|
componentsList.Add(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var components = componentsList.ToArray();
|
||||||
|
this.TotalComponentsCount = components.Length;
|
||||||
|
|
||||||
|
this.literalsToMatch = new string[this.TotalComponentsCount];
|
||||||
|
this.variablesNames = new string[this.TotalComponentsCount];
|
||||||
|
this.isWildcard = new bool[this.TotalComponentsCount];
|
||||||
|
this.componentsWithSeparators = hasSeparators.ToArray();
|
||||||
|
this.PathComponentsCount = this.componentsWithSeparators.Length;
|
||||||
|
string firstLiteralMatch = null;
|
||||||
|
|
||||||
|
var sbHashKey = new StringBuilder();
|
||||||
|
for (var i = 0; i < components.Length; i++)
|
||||||
|
{
|
||||||
|
var component = components[i];
|
||||||
|
|
||||||
|
if (component.StartsWith(VariablePrefix))
|
||||||
|
{
|
||||||
|
var variableName = component.Substring(1, component.Length - 2);
|
||||||
|
if (variableName[variableName.Length - 1] == WildCardChar)
|
||||||
|
{
|
||||||
|
this.isWildcard[i] = true;
|
||||||
|
variableName = variableName.Substring(0, variableName.Length - 1);
|
||||||
|
}
|
||||||
|
this.variablesNames[i] = variableName;
|
||||||
|
this.VariableArgsCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.literalsToMatch[i] = component.ToLower();
|
||||||
|
sbHashKey.Append(i + PathSeperatorChar.ToString() + this.literalsToMatch);
|
||||||
|
|
||||||
|
if (firstLiteralMatch == null)
|
||||||
|
{
|
||||||
|
firstLiteralMatch = this.literalsToMatch[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < components.Length - 1; i++)
|
||||||
|
{
|
||||||
|
if (!this.isWildcard[i]) continue;
|
||||||
|
if (this.literalsToMatch[i + 1] == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"A wildcard path component must be at the end of the path or followed by a literal path component.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wildcardCount = this.isWildcard.Count(x => x);
|
||||||
|
this.IsWildCardPath = this.wildcardCount > 0;
|
||||||
|
|
||||||
|
this.FirstMatchHashKey = !this.IsWildCardPath
|
||||||
|
? this.PathComponentsCount + PathSeperator + firstLiteralMatch
|
||||||
|
: WildCardChar + PathSeperator + firstLiteralMatch;
|
||||||
|
|
||||||
|
this.IsValid = sbHashKey.Length > 0;
|
||||||
|
this.UniqueMatchHashKey = sbHashKey.ToString();
|
||||||
|
|
||||||
|
this.typeDeserializer = new StringMapTypeDeserializer(this.RequestType);
|
||||||
|
RegisterCaseInsenstivePropertyNameMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterCaseInsenstivePropertyNameMappings()
|
||||||
|
{
|
||||||
|
foreach (var propertyInfo in RequestType.GetSerializableProperties())
|
||||||
|
{
|
||||||
|
var propertyName = propertyInfo.Name;
|
||||||
|
propertyNamesMap.Add(propertyName.ToLower(), propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsValid { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provide for quick lookups based on hashes that can be determined from a request url
|
||||||
|
/// </summary>
|
||||||
|
public string FirstMatchHashKey { get; private set; }
|
||||||
|
|
||||||
|
public string UniqueMatchHashKey { get; private set; }
|
||||||
|
|
||||||
|
private readonly StringMapTypeDeserializer typeDeserializer;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
public static Func<RestPath, string, string[], int> CalculateMatchScore { get; set; }
|
||||||
|
|
||||||
|
public int MatchScore(string httpMethod, string[] withPathInfoParts)
|
||||||
|
{
|
||||||
|
if (CalculateMatchScore != null)
|
||||||
|
return CalculateMatchScore(this, httpMethod, withPathInfoParts);
|
||||||
|
|
||||||
|
int wildcardMatchCount;
|
||||||
|
var isMatch = IsMatch(httpMethod, withPathInfoParts, out wildcardMatchCount);
|
||||||
|
if (!isMatch) return -1;
|
||||||
|
|
||||||
|
var score = 0;
|
||||||
|
|
||||||
|
//Routes with least wildcard matches get the highest score
|
||||||
|
score += Math.Max((100 - wildcardMatchCount), 1) * 1000;
|
||||||
|
|
||||||
|
//Routes with less variable (and more literal) matches
|
||||||
|
score += Math.Max((10 - VariableArgsCount), 1) * 100;
|
||||||
|
|
||||||
|
//Exact verb match is better than ANY
|
||||||
|
var exactVerb = httpMethod == AllowedVerbs;
|
||||||
|
score += exactVerb ? 10 : 1;
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For performance withPathInfoParts should already be a lower case string
|
||||||
|
/// to minimize redundant matching operations.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpMethod"></param>
|
||||||
|
/// <param name="withPathInfoParts"></param>
|
||||||
|
/// <param name="wildcardMatchCount"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool IsMatch(string httpMethod, string[] withPathInfoParts, out int wildcardMatchCount)
|
||||||
|
{
|
||||||
|
wildcardMatchCount = 0;
|
||||||
|
|
||||||
|
if (withPathInfoParts.Length != this.PathComponentsCount && !this.IsWildCardPath) return false;
|
||||||
|
if (!this.allowsAllVerbs && !this.allowedVerbs.Contains(httpMethod.ToUpper())) return false;
|
||||||
|
|
||||||
|
if (!ExplodeComponents(ref withPathInfoParts)) return false;
|
||||||
|
if (this.TotalComponentsCount != withPathInfoParts.Length && !this.IsWildCardPath) return false;
|
||||||
|
|
||||||
|
int pathIx = 0;
|
||||||
|
for (var i = 0; i < this.TotalComponentsCount; i++)
|
||||||
|
{
|
||||||
|
if (this.isWildcard[i])
|
||||||
|
{
|
||||||
|
if (i < this.TotalComponentsCount - 1)
|
||||||
|
{
|
||||||
|
// Continue to consume up until a match with the next literal
|
||||||
|
while (pathIx < withPathInfoParts.Length && withPathInfoParts[pathIx] != this.literalsToMatch[i + 1])
|
||||||
|
{
|
||||||
|
pathIx++;
|
||||||
|
wildcardMatchCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure there are still enough parts left to match the remainder
|
||||||
|
if ((withPathInfoParts.Length - pathIx) < (this.TotalComponentsCount - i - 1))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// A wildcard at the end matches the remainder of path
|
||||||
|
wildcardMatchCount += withPathInfoParts.Length - pathIx;
|
||||||
|
pathIx = withPathInfoParts.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var literalToMatch = this.literalsToMatch[i];
|
||||||
|
if (literalToMatch == null)
|
||||||
|
{
|
||||||
|
// Matching an ordinary (non-wildcard) variable consumes a single part
|
||||||
|
pathIx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (withPathInfoParts.Length <= pathIx || withPathInfoParts[pathIx] != literalToMatch) return false;
|
||||||
|
pathIx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathIx == withPathInfoParts.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ExplodeComponents(ref string[] withPathInfoParts)
|
||||||
|
{
|
||||||
|
var totalComponents = new List<string>();
|
||||||
|
for (var i = 0; i < withPathInfoParts.Length; i++)
|
||||||
|
{
|
||||||
|
var component = withPathInfoParts[i];
|
||||||
|
if (string.IsNullOrEmpty(component)) continue;
|
||||||
|
|
||||||
|
if (this.PathComponentsCount != this.TotalComponentsCount
|
||||||
|
&& this.componentsWithSeparators[i])
|
||||||
|
{
|
||||||
|
var subComponents = component.Split(ComponentSeperator);
|
||||||
|
if (subComponents.Length < 2) return false;
|
||||||
|
totalComponents.AddRange(subComponents);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
totalComponents.Add(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withPathInfoParts = totalComponents.ToArray();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object CreateRequest(string pathInfo, Dictionary<string, string> queryStringAndFormData, object fromInstance)
|
||||||
|
{
|
||||||
|
var requestComponents = pathInfo.Split(PathSeperatorChar)
|
||||||
|
.Where(x => !string.IsNullOrEmpty(x)).ToArray();
|
||||||
|
|
||||||
|
ExplodeComponents(ref requestComponents);
|
||||||
|
|
||||||
|
if (requestComponents.Length != this.TotalComponentsCount)
|
||||||
|
{
|
||||||
|
var isValidWildCardPath = this.IsWildCardPath
|
||||||
|
&& requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount;
|
||||||
|
|
||||||
|
if (!isValidWildCardPath)
|
||||||
|
throw new ArgumentException(string.Format(
|
||||||
|
"Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
|
||||||
|
pathInfo, this.restPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestKeyValuesMap = new Dictionary<string, string>();
|
||||||
|
var pathIx = 0;
|
||||||
|
for (var i = 0; i < this.TotalComponentsCount; i++)
|
||||||
|
{
|
||||||
|
var variableName = this.variablesNames[i];
|
||||||
|
if (variableName == null)
|
||||||
|
{
|
||||||
|
pathIx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string propertyNameOnRequest;
|
||||||
|
if (!this.propertyNamesMap.TryGetValue(variableName.ToLower(), out propertyNameOnRequest))
|
||||||
|
{
|
||||||
|
if (string.Equals("ignore", variableName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
pathIx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException("Could not find property "
|
||||||
|
+ variableName + " on " + RequestType.GetOperationName());
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = requestComponents.Length > pathIx ? requestComponents[pathIx] : null; //wildcard has arg mismatch
|
||||||
|
if (value != null && this.isWildcard[i])
|
||||||
|
{
|
||||||
|
if (i == this.TotalComponentsCount - 1)
|
||||||
|
{
|
||||||
|
// Wildcard at end of path definition consumes all the rest
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append(value);
|
||||||
|
for (var j = pathIx + 1; j < requestComponents.Length; j++)
|
||||||
|
{
|
||||||
|
sb.Append(PathSeperatorChar + requestComponents[j]);
|
||||||
|
}
|
||||||
|
value = sb.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Wildcard in middle of path definition consumes up until it
|
||||||
|
// hits a match for the next element in the definition (which must be a literal)
|
||||||
|
// It may consume 0 or more path parts
|
||||||
|
var stopLiteral = i == this.TotalComponentsCount - 1 ? null : this.literalsToMatch[i + 1];
|
||||||
|
if (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append(value);
|
||||||
|
pathIx++;
|
||||||
|
while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
sb.Append(PathSeperatorChar + requestComponents[pathIx++]);
|
||||||
|
}
|
||||||
|
value = sb.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Variable consumes single path item
|
||||||
|
pathIx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestKeyValuesMap[propertyNameOnRequest] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryStringAndFormData != null)
|
||||||
|
{
|
||||||
|
//Query String and form data can override variable path matches
|
||||||
|
//path variables < query string < form data
|
||||||
|
foreach (var name in queryStringAndFormData)
|
||||||
|
{
|
||||||
|
requestKeyValuesMap[name.Key] = name.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return UniqueMatchHashKey.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
220
ServiceStack/Host/ServiceController.cs
Normal file
220
ServiceStack/Host/ServiceController.cs
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
|
||||||
|
namespace ServiceStack.Host
|
||||||
|
{
|
||||||
|
public delegate Task<object> InstanceExecFn(IRequest requestContext, object intance, object request);
|
||||||
|
public delegate object ActionInvokerFn(object intance, object request);
|
||||||
|
public delegate void VoidActionInvokerFn(object intance, object request);
|
||||||
|
|
||||||
|
public class ServiceController
|
||||||
|
{
|
||||||
|
private readonly Func<IEnumerable<Type>> _resolveServicesFn;
|
||||||
|
|
||||||
|
public ServiceController(Func<IEnumerable<Type>> resolveServicesFn)
|
||||||
|
{
|
||||||
|
_resolveServicesFn = resolveServicesFn;
|
||||||
|
this.RequestTypeFactoryMap = new Dictionary<Type, Func<IRequest, object>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<Type, Func<IRequest, object>> RequestTypeFactoryMap { get; set; }
|
||||||
|
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
foreach (var serviceType in _resolveServicesFn())
|
||||||
|
{
|
||||||
|
RegisterService(serviceType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type[] GetGenericArguments(Type type)
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().IsGenericTypeDefinition
|
||||||
|
? type.GetTypeInfo().GenericTypeParameters
|
||||||
|
: type.GetTypeInfo().GenericTypeArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterService(Type serviceType)
|
||||||
|
{
|
||||||
|
var processedReqs = new HashSet<Type>();
|
||||||
|
|
||||||
|
var actions = ServiceExecGeneral.Reset(serviceType);
|
||||||
|
|
||||||
|
var requiresRequestStreamTypeInfo = typeof(IRequiresRequestStream).GetTypeInfo();
|
||||||
|
|
||||||
|
var appHost = ServiceStackHost.Instance;
|
||||||
|
foreach (var mi in serviceType.GetActions())
|
||||||
|
{
|
||||||
|
var requestType = mi.GetParameters()[0].ParameterType;
|
||||||
|
if (processedReqs.Contains(requestType)) continue;
|
||||||
|
processedReqs.Add(requestType);
|
||||||
|
|
||||||
|
ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions);
|
||||||
|
|
||||||
|
var returnMarker = requestType.GetTypeWithGenericTypeDefinitionOf(typeof(IReturn<>));
|
||||||
|
var responseType = returnMarker != null ?
|
||||||
|
GetGenericArguments(returnMarker)[0]
|
||||||
|
: mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void) ?
|
||||||
|
mi.ReturnType
|
||||||
|
: Type.GetType(requestType.FullName + "Response");
|
||||||
|
|
||||||
|
RegisterRestPaths(requestType);
|
||||||
|
|
||||||
|
appHost.Metadata.Add(serviceType, requestType, responseType);
|
||||||
|
|
||||||
|
if (requiresRequestStreamTypeInfo.IsAssignableFrom(requestType.GetTypeInfo()))
|
||||||
|
{
|
||||||
|
this.RequestTypeFactoryMap[requestType] = req =>
|
||||||
|
{
|
||||||
|
var restPath = req.GetRoute();
|
||||||
|
var request = RestHandler.CreateRequest(req, restPath, req.GetRequestParams(), ServiceStackHost.Instance.CreateInstance(requestType));
|
||||||
|
|
||||||
|
var rawReq = (IRequiresRequestStream)request;
|
||||||
|
rawReq.RequestStream = req.InputStream;
|
||||||
|
return rawReq;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Dictionary<string, List<RestPath>> RestPathMap = new Dictionary<string, List<RestPath>>();
|
||||||
|
|
||||||
|
public void RegisterRestPaths(Type requestType)
|
||||||
|
{
|
||||||
|
var appHost = ServiceStackHost.Instance;
|
||||||
|
var attrs = appHost.GetRouteAttributes(requestType);
|
||||||
|
foreach (MediaBrowser.Model.Services.RouteAttribute attr in attrs)
|
||||||
|
{
|
||||||
|
var restPath = new RestPath(requestType, attr.Path, attr.Verbs, attr.Summary, attr.Notes);
|
||||||
|
|
||||||
|
if (!restPath.IsValid)
|
||||||
|
throw new NotSupportedException(string.Format(
|
||||||
|
"RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.GetOperationName()));
|
||||||
|
|
||||||
|
RegisterRestPath(restPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly char[] InvalidRouteChars = new[] { '?', '&' };
|
||||||
|
|
||||||
|
public void RegisterRestPath(RestPath restPath)
|
||||||
|
{
|
||||||
|
if (!restPath.Path.StartsWith("/"))
|
||||||
|
throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetOperationName()));
|
||||||
|
if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1)
|
||||||
|
throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. " +
|
||||||
|
"See https://github.com/ServiceStack/ServiceStack/wiki/Routing for info on valid routes.", restPath.Path, restPath.RequestType.GetOperationName()));
|
||||||
|
|
||||||
|
List<RestPath> pathsAtFirstMatch;
|
||||||
|
if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch))
|
||||||
|
{
|
||||||
|
pathsAtFirstMatch = new List<RestPath>();
|
||||||
|
RestPathMap[restPath.FirstMatchHashKey] = pathsAtFirstMatch;
|
||||||
|
}
|
||||||
|
pathsAtFirstMatch.Add(restPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AfterInit()
|
||||||
|
{
|
||||||
|
var appHost = ServiceStackHost.Instance;
|
||||||
|
|
||||||
|
//Register any routes configured on Metadata.Routes
|
||||||
|
foreach (var restPath in appHost.RestPaths)
|
||||||
|
{
|
||||||
|
RegisterRestPath(restPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sync the RestPaths collections
|
||||||
|
appHost.RestPaths.Clear();
|
||||||
|
appHost.RestPaths.AddRange(RestPathMap.Values.SelectMany(x => x));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestPath GetRestPathForRequest(string httpMethod, string pathInfo)
|
||||||
|
{
|
||||||
|
var matchUsingPathParts = RestPath.GetPathPartsForMatching(pathInfo);
|
||||||
|
|
||||||
|
List<RestPath> firstMatches;
|
||||||
|
|
||||||
|
var yieldedHashMatches = RestPath.GetFirstMatchHashKeys(matchUsingPathParts);
|
||||||
|
foreach (var potentialHashMatch in yieldedHashMatches)
|
||||||
|
{
|
||||||
|
if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue;
|
||||||
|
|
||||||
|
var bestScore = -1;
|
||||||
|
foreach (var restPath in firstMatches)
|
||||||
|
{
|
||||||
|
var score = restPath.MatchScore(httpMethod, matchUsingPathParts);
|
||||||
|
if (score > bestScore) bestScore = score;
|
||||||
|
}
|
||||||
|
if (bestScore > 0)
|
||||||
|
{
|
||||||
|
foreach (var restPath in firstMatches)
|
||||||
|
{
|
||||||
|
if (bestScore == restPath.MatchScore(httpMethod, matchUsingPathParts))
|
||||||
|
return restPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var yieldedWildcardMatches = RestPath.GetFirstMatchWildCardHashKeys(matchUsingPathParts);
|
||||||
|
foreach (var potentialHashMatch in yieldedWildcardMatches)
|
||||||
|
{
|
||||||
|
if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue;
|
||||||
|
|
||||||
|
var bestScore = -1;
|
||||||
|
foreach (var restPath in firstMatches)
|
||||||
|
{
|
||||||
|
var score = restPath.MatchScore(httpMethod, matchUsingPathParts);
|
||||||
|
if (score > bestScore) bestScore = score;
|
||||||
|
}
|
||||||
|
if (bestScore > 0)
|
||||||
|
{
|
||||||
|
foreach (var restPath in firstMatches)
|
||||||
|
{
|
||||||
|
if (bestScore == restPath.MatchScore(httpMethod, matchUsingPathParts))
|
||||||
|
return restPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<object> Execute(object requestDto, IRequest req)
|
||||||
|
{
|
||||||
|
req.Dto = requestDto;
|
||||||
|
var requestType = requestDto.GetType();
|
||||||
|
req.OperationName = requestType.Name;
|
||||||
|
|
||||||
|
var serviceType = ServiceStackHost.Instance.Metadata.GetServiceTypeByRequest(requestType);
|
||||||
|
|
||||||
|
var service = ServiceStackHost.Instance.CreateInstance(serviceType);
|
||||||
|
|
||||||
|
//var service = typeFactory.CreateInstance(serviceType);
|
||||||
|
|
||||||
|
var serviceRequiresContext = service as IRequiresRequest;
|
||||||
|
if (serviceRequiresContext != null)
|
||||||
|
{
|
||||||
|
serviceRequiresContext.Request = req;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.Dto == null) // Don't override existing batched DTO[]
|
||||||
|
req.Dto = requestDto;
|
||||||
|
|
||||||
|
//Executes the service and returns the result
|
||||||
|
var response = await ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetOperationName()).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (req.Response.Dto == null)
|
||||||
|
req.Response.Dto = response;
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
156
ServiceStack/Host/ServiceExec.cs
Normal file
156
ServiceStack/Host/ServiceExec.cs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
//Copyright (c) Service Stack LLC. All Rights Reserved.
|
||||||
|
//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
|
||||||
|
namespace ServiceStack.Host
|
||||||
|
{
|
||||||
|
public static class ServiceExecExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<MethodInfo> GetActions(this Type serviceType)
|
||||||
|
{
|
||||||
|
foreach (var mi in serviceType.GetRuntimeMethods().Where(i => i.IsPublic && !i.IsStatic))
|
||||||
|
{
|
||||||
|
if (mi.GetParameters().Length != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var actionName = mi.Name.ToUpper();
|
||||||
|
if (!HttpMethods.AllVerbs.Contains(actionName) && actionName != ActionContext.AnyAction)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
yield return mi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class ServiceExecGeneral
|
||||||
|
{
|
||||||
|
public static Dictionary<string, ActionContext> execMap = new Dictionary<string, ActionContext>();
|
||||||
|
|
||||||
|
public static void CreateServiceRunnersFor(Type requestType, List<ActionContext> actions)
|
||||||
|
{
|
||||||
|
foreach (var actionCtx in actions)
|
||||||
|
{
|
||||||
|
if (execMap.ContainsKey(actionCtx.Id)) continue;
|
||||||
|
|
||||||
|
execMap[actionCtx.Id] = actionCtx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<object> Execute(Type serviceType, IRequest request, object instance, object requestDto, string requestName)
|
||||||
|
{
|
||||||
|
var actionName = request.Verb
|
||||||
|
?? HttpMethods.Post; //MQ Services
|
||||||
|
|
||||||
|
ActionContext actionContext;
|
||||||
|
if (ServiceExecGeneral.execMap.TryGetValue(ActionContext.Key(serviceType, actionName, requestName), out actionContext)
|
||||||
|
|| ServiceExecGeneral.execMap.TryGetValue(ActionContext.AnyKey(serviceType, requestName), out actionContext))
|
||||||
|
{
|
||||||
|
if (actionContext.RequestFilters != null)
|
||||||
|
{
|
||||||
|
foreach (var requestFilter in actionContext.RequestFilters)
|
||||||
|
{
|
||||||
|
requestFilter.RequestFilter(request, request.Response, requestDto);
|
||||||
|
if (request.Response.IsClosed) return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = actionContext.ServiceAction(instance, requestDto);
|
||||||
|
|
||||||
|
var taskResponse = response as Task;
|
||||||
|
if (taskResponse != null)
|
||||||
|
{
|
||||||
|
await taskResponse.ConfigureAwait(false);
|
||||||
|
response = ServiceStackHost.Instance.GetTaskResult(taskResponse, requestName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLower();
|
||||||
|
throw new NotImplementedException(string.Format("Could not find method named {1}({0}) or Any({0}) on Service {2}", requestDto.GetType().GetOperationName(), expectedMethodName, serviceType.GetOperationName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ActionContext> Reset(Type serviceType)
|
||||||
|
{
|
||||||
|
var actions = new List<ActionContext>();
|
||||||
|
|
||||||
|
foreach (var mi in serviceType.GetActions())
|
||||||
|
{
|
||||||
|
var actionName = mi.Name.ToUpper();
|
||||||
|
var args = mi.GetParameters();
|
||||||
|
|
||||||
|
var requestType = args[0].ParameterType;
|
||||||
|
var actionCtx = new ActionContext
|
||||||
|
{
|
||||||
|
Id = ActionContext.Key(serviceType, actionName, requestType.GetOperationName())
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
actionCtx.ServiceAction = CreateExecFn(serviceType, requestType, mi);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//Potential problems with MONO, using reflection for fallback
|
||||||
|
actionCtx.ServiceAction = (service, request) =>
|
||||||
|
mi.Invoke(service, new[] { request });
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqFilters = new List<IHasRequestFilter>();
|
||||||
|
|
||||||
|
foreach (var attr in mi.GetCustomAttributes(true))
|
||||||
|
{
|
||||||
|
var hasReqFilter = attr as IHasRequestFilter;
|
||||||
|
|
||||||
|
if (hasReqFilter != null)
|
||||||
|
reqFilters.Add(hasReqFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reqFilters.Count > 0)
|
||||||
|
actionCtx.RequestFilters = reqFilters.OrderBy(i => i.Priority).ToArray();
|
||||||
|
|
||||||
|
actions.Add(actionCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActionInvokerFn CreateExecFn(Type serviceType, Type requestType, MethodInfo mi)
|
||||||
|
{
|
||||||
|
var serviceParam = Expression.Parameter(typeof(object), "serviceObj");
|
||||||
|
var serviceStrong = Expression.Convert(serviceParam, serviceType);
|
||||||
|
|
||||||
|
var requestDtoParam = Expression.Parameter(typeof(object), "requestDto");
|
||||||
|
var requestDtoStrong = Expression.Convert(requestDtoParam, requestType);
|
||||||
|
|
||||||
|
Expression callExecute = Expression.Call(
|
||||||
|
serviceStrong, mi, requestDtoStrong);
|
||||||
|
|
||||||
|
if (mi.ReturnType != typeof(void))
|
||||||
|
{
|
||||||
|
var executeFunc = Expression.Lambda<ActionInvokerFn>
|
||||||
|
(callExecute, serviceParam, requestDtoParam).Compile();
|
||||||
|
|
||||||
|
return executeFunc;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var executeFunc = Expression.Lambda<VoidActionInvokerFn>
|
||||||
|
(callExecute, serviceParam, requestDtoParam).Compile();
|
||||||
|
|
||||||
|
return (service, request) =>
|
||||||
|
{
|
||||||
|
executeFunc(service, request);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
ServiceStack/Host/ServiceMetadata.cs
Normal file
27
ServiceStack/Host/ServiceMetadata.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ServiceStack.Host
|
||||||
|
{
|
||||||
|
public class ServiceMetadata
|
||||||
|
{
|
||||||
|
public ServiceMetadata()
|
||||||
|
{
|
||||||
|
this.OperationsMap = new Dictionary<Type, Type>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<Type, Type> OperationsMap { get; protected set; }
|
||||||
|
|
||||||
|
public void Add(Type serviceType, Type requestType, Type responseType)
|
||||||
|
{
|
||||||
|
this.OperationsMap[requestType] = serviceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type GetServiceTypeByRequest(Type requestType)
|
||||||
|
{
|
||||||
|
Type serviceType;
|
||||||
|
OperationsMap.TryGetValue(requestType, out serviceType);
|
||||||
|
return serviceType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
ServiceStack/HttpHandlerFactory.cs
Normal file
27
ServiceStack/HttpHandlerFactory.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
using ServiceStack.Host;
|
||||||
|
|
||||||
|
namespace ServiceStack
|
||||||
|
{
|
||||||
|
public class HttpHandlerFactory
|
||||||
|
{
|
||||||
|
// Entry point for HttpListener
|
||||||
|
public static RestHandler GetHandler(IHttpRequest httpReq)
|
||||||
|
{
|
||||||
|
var pathInfo = httpReq.PathInfo;
|
||||||
|
|
||||||
|
var pathParts = pathInfo.TrimStart('/').Split('/');
|
||||||
|
if (pathParts.Length == 0) return null;
|
||||||
|
|
||||||
|
string contentType;
|
||||||
|
var restPath = RestHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out contentType);
|
||||||
|
if (restPath != null)
|
||||||
|
return new RestHandler { RestPath = restPath, RequestName = restPath.RequestType.GetOperationName(), ResponseContentType = contentType };
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
ServiceStack/HttpRequestExtensions.cs
Normal file
127
ServiceStack/HttpRequestExtensions.cs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
using ServiceStack.Host;
|
||||||
|
|
||||||
|
namespace ServiceStack
|
||||||
|
{
|
||||||
|
public static class HttpRequestExtensions
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
Input: http://localhost:96/Cambia3/Temp/Test.aspx/path/info?q=item#fragment
|
||||||
|
|
||||||
|
Some HttpRequest path and URL properties:
|
||||||
|
Request.ApplicationPath: /Cambia3
|
||||||
|
Request.CurrentExecutionFilePath: /Cambia3/Temp/Test.aspx
|
||||||
|
Request.FilePath: /Cambia3/Temp/Test.aspx
|
||||||
|
Request.Path: /Cambia3/Temp/Test.aspx/path/info
|
||||||
|
Request.PathInfo: /path/info
|
||||||
|
Request.PhysicalApplicationPath: D:\Inetpub\wwwroot\CambiaWeb\Cambia3\
|
||||||
|
Request.QueryString: /Cambia3/Temp/Test.aspx/path/info?query=arg
|
||||||
|
Request.Url.AbsolutePath: /Cambia3/Temp/Test.aspx/path/info
|
||||||
|
Request.Url.AbsoluteUri: http://localhost:96/Cambia3/Temp/Test.aspx/path/info?query=arg
|
||||||
|
Request.Url.Fragment:
|
||||||
|
Request.Url.Host: localhost
|
||||||
|
Request.Url.LocalPath: /Cambia3/Temp/Test.aspx/path/info
|
||||||
|
Request.Url.PathAndQuery: /Cambia3/Temp/Test.aspx/path/info?query=arg
|
||||||
|
Request.Url.Port: 96
|
||||||
|
Request.Url.Query: ?query=arg
|
||||||
|
Request.Url.Scheme: http
|
||||||
|
Request.Url.Segments: /
|
||||||
|
Cambia3/
|
||||||
|
Temp/
|
||||||
|
Test.aspx/
|
||||||
|
path/
|
||||||
|
info
|
||||||
|
* */
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duplicate Params are given a unique key by appending a #1 suffix
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, string> GetRequestParams(this IRequest request)
|
||||||
|
{
|
||||||
|
var map = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
foreach (var name in request.QueryString.Keys)
|
||||||
|
{
|
||||||
|
if (name == null) continue; //thank you ASP.NET
|
||||||
|
|
||||||
|
var values = request.QueryString.GetValues(name);
|
||||||
|
if (values.Length == 1)
|
||||||
|
{
|
||||||
|
map[name] = values[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
map[name + (i == 0 ? "" : "#" + i)] = values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((request.Verb == HttpMethods.Post || request.Verb == HttpMethods.Put)
|
||||||
|
&& request.FormData != null)
|
||||||
|
{
|
||||||
|
foreach (var name in request.FormData.Keys)
|
||||||
|
{
|
||||||
|
if (name == null) continue; //thank you ASP.NET
|
||||||
|
|
||||||
|
var values = request.FormData.GetValues(name);
|
||||||
|
if (values.Length == 1)
|
||||||
|
{
|
||||||
|
map[name] = values[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
map[name + (i == 0 ? "" : "#" + i)] = values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duplicate params have their values joined together in a comma-delimited string
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, string> GetFlattenedRequestParams(this IRequest request)
|
||||||
|
{
|
||||||
|
var map = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
foreach (var name in request.QueryString.Keys)
|
||||||
|
{
|
||||||
|
if (name == null) continue; //thank you ASP.NET
|
||||||
|
map[name] = request.QueryString[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((request.Verb == HttpMethods.Post || request.Verb == HttpMethods.Put)
|
||||||
|
&& request.FormData != null)
|
||||||
|
{
|
||||||
|
foreach (var name in request.FormData.Keys)
|
||||||
|
{
|
||||||
|
if (name == null) continue; //thank you ASP.NET
|
||||||
|
map[name] = request.FormData[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetRoute(this IRequest req, RestPath route)
|
||||||
|
{
|
||||||
|
req.Items["__route"] = route;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RestPath GetRoute(this IRequest req)
|
||||||
|
{
|
||||||
|
object route;
|
||||||
|
req.Items.TryGetValue("__route", out route);
|
||||||
|
return route as RestPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
237
ServiceStack/HttpResponseExtensionsInternal.cs
Normal file
237
ServiceStack/HttpResponseExtensionsInternal.cs
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
//Copyright (c) Service Stack LLC. All Rights Reserved.
|
||||||
|
//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
using ServiceStack.Host;
|
||||||
|
|
||||||
|
namespace ServiceStack
|
||||||
|
{
|
||||||
|
public static class HttpResponseExtensionsInternal
|
||||||
|
{
|
||||||
|
public static async Task<bool> WriteToOutputStream(IResponse response, object result)
|
||||||
|
{
|
||||||
|
var asyncStreamWriter = result as IAsyncStreamWriter;
|
||||||
|
if (asyncStreamWriter != null)
|
||||||
|
{
|
||||||
|
await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var streamWriter = result as IStreamWriter;
|
||||||
|
if (streamWriter != null)
|
||||||
|
{
|
||||||
|
streamWriter.WriteTo(response.OutputStream);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = result as Stream;
|
||||||
|
if (stream != null)
|
||||||
|
{
|
||||||
|
WriteTo(stream, response.OutputStream);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = result as byte[];
|
||||||
|
if (bytes != null)
|
||||||
|
{
|
||||||
|
response.ContentType = "application/octet-stream";
|
||||||
|
response.SetContentLength(bytes.Length);
|
||||||
|
|
||||||
|
response.OutputStream.Write(bytes, 0, bytes.Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long WriteTo(Stream inStream, Stream outStream)
|
||||||
|
{
|
||||||
|
var memoryStream = inStream as MemoryStream;
|
||||||
|
if (memoryStream != null)
|
||||||
|
{
|
||||||
|
memoryStream.WriteTo(outStream);
|
||||||
|
return memoryStream.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new byte[4096];
|
||||||
|
long total = 0;
|
||||||
|
int bytesRead;
|
||||||
|
|
||||||
|
while ((bytesRead = inStream.Read(data, 0, data.Length)) > 0)
|
||||||
|
{
|
||||||
|
outStream.Write(data, 0, bytesRead);
|
||||||
|
total += bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// End a ServiceStack Request with no content
|
||||||
|
/// </summary>
|
||||||
|
public static void EndRequestWithNoContent(this IResponse httpRes)
|
||||||
|
{
|
||||||
|
if (httpRes.StatusCode == (int)HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
httpRes.StatusCode = (int)HttpStatusCode.NoContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpRes.SetContentLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task WriteToResponse(this IResponse httpRes, MediaBrowser.Model.Services.IRequest httpReq, object result)
|
||||||
|
{
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
httpRes.EndRequestWithNoContent();
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpResult = result as IHttpResult;
|
||||||
|
if (httpResult != null)
|
||||||
|
{
|
||||||
|
httpResult.RequestContext = httpReq;
|
||||||
|
httpReq.ResponseContentType = httpResult.ContentType ?? httpReq.ResponseContentType;
|
||||||
|
var httpResSerializer = ContentTypes.Instance.GetResponseSerializer(httpReq.ResponseContentType);
|
||||||
|
return httpRes.WriteToResponse(httpResult, httpResSerializer, httpReq);
|
||||||
|
}
|
||||||
|
|
||||||
|
var serializer = ContentTypes.Instance.GetResponseSerializer(httpReq.ResponseContentType);
|
||||||
|
return httpRes.WriteToResponse(result, serializer, httpReq);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object GetDto(object response)
|
||||||
|
{
|
||||||
|
if (response == null) return null;
|
||||||
|
var httpResult = response as IHttpResult;
|
||||||
|
return httpResult != null ? httpResult.Response : response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes to response.
|
||||||
|
/// Response headers are customizable by implementing IHasHeaders an returning Dictionary of Http headers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response">The response.</param>
|
||||||
|
/// <param name="result">Whether or not it was implicity handled by ServiceStack's built-in handlers.</param>
|
||||||
|
/// <param name="defaultAction">The default action.</param>
|
||||||
|
/// <param name="request">The serialization context.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task WriteToResponse(this IResponse response, object result, Action<IRequest, object, IResponse> defaultAction, MediaBrowser.Model.Services.IRequest request)
|
||||||
|
{
|
||||||
|
var defaultContentType = request.ResponseContentType;
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
response.EndRequestWithNoContent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpResult = result as IHttpResult;
|
||||||
|
if (httpResult != null)
|
||||||
|
{
|
||||||
|
if (httpResult.RequestContext == null)
|
||||||
|
httpResult.RequestContext = request;
|
||||||
|
|
||||||
|
response.Dto = response.Dto ?? GetDto(httpResult);
|
||||||
|
|
||||||
|
response.StatusCode = httpResult.Status;
|
||||||
|
response.StatusDescription = httpResult.StatusDescription ?? httpResult.StatusCode.ToString();
|
||||||
|
if (string.IsNullOrEmpty(httpResult.ContentType))
|
||||||
|
{
|
||||||
|
httpResult.ContentType = defaultContentType;
|
||||||
|
}
|
||||||
|
response.ContentType = httpResult.ContentType;
|
||||||
|
|
||||||
|
if (httpResult.Cookies != null)
|
||||||
|
{
|
||||||
|
var httpRes = response as IHttpResponse;
|
||||||
|
if (httpRes != null)
|
||||||
|
{
|
||||||
|
foreach (var cookie in httpResult.Cookies)
|
||||||
|
{
|
||||||
|
httpRes.SetCookie(cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.Dto = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mono Error: Exception: Method not found: 'System.Web.HttpResponse.get_Headers' */
|
||||||
|
var responseOptions = result as IHasHeaders;
|
||||||
|
if (responseOptions != null)
|
||||||
|
{
|
||||||
|
//Reserving options with keys in the format 'xx.xxx' (No Http headers contain a '.' so its a safe restriction)
|
||||||
|
const string reservedOptions = ".";
|
||||||
|
|
||||||
|
foreach (var responseHeaders in responseOptions.Headers)
|
||||||
|
{
|
||||||
|
if (responseHeaders.Key.Contains(reservedOptions)) continue;
|
||||||
|
if (responseHeaders.Key == "Content-Length")
|
||||||
|
{
|
||||||
|
response.SetContentLength(long.Parse(responseHeaders.Value));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.AddHeader(responseHeaders.Key, responseHeaders.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//ContentType='text/html' is the default for a HttpResponse
|
||||||
|
//Do not override if another has been set
|
||||||
|
if (response.ContentType == null || response.ContentType == "text/html")
|
||||||
|
{
|
||||||
|
response.ContentType = defaultContentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new HashSet<string> { "application/json", }.Contains(response.ContentType))
|
||||||
|
{
|
||||||
|
response.ContentType += "; charset=utf-8";
|
||||||
|
}
|
||||||
|
|
||||||
|
var disposableResult = result as IDisposable;
|
||||||
|
var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false);
|
||||||
|
if (writeToOutputStreamResult)
|
||||||
|
{
|
||||||
|
response.Flush(); //required for Compression
|
||||||
|
if (disposableResult != null) disposableResult.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpResult != null)
|
||||||
|
result = httpResult.Response;
|
||||||
|
|
||||||
|
var responseText = result as string;
|
||||||
|
if (responseText != null)
|
||||||
|
{
|
||||||
|
if (response.ContentType == null || response.ContentType == "text/html")
|
||||||
|
response.ContentType = defaultContentType;
|
||||||
|
response.Write(responseText);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultAction == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("defaultAction", String.Format(
|
||||||
|
"As result '{0}' is not a supported responseType, a defaultAction must be supplied",
|
||||||
|
(result != null ? result.GetType().GetOperationName() : "")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
defaultAction(request, result, response);
|
||||||
|
|
||||||
|
if (disposableResult != null)
|
||||||
|
disposableResult.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
250
ServiceStack/HttpResult.cs
Normal file
250
ServiceStack/HttpResult.cs
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
using ServiceStack.Host;
|
||||||
|
|
||||||
|
namespace ServiceStack
|
||||||
|
{
|
||||||
|
public class HttpResult
|
||||||
|
: IHttpResult, IAsyncStreamWriter
|
||||||
|
{
|
||||||
|
public HttpResult()
|
||||||
|
: this((object)null, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResult(object response)
|
||||||
|
: this(response, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResult(object response, string contentType)
|
||||||
|
: this(response, contentType, HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResult(HttpStatusCode statusCode, string statusDescription)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
StatusCode = statusCode;
|
||||||
|
StatusDescription = statusDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResult(object response, HttpStatusCode statusCode)
|
||||||
|
: this(response, null, statusCode)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public HttpResult(object response, string contentType, HttpStatusCode statusCode)
|
||||||
|
{
|
||||||
|
this.Headers = new Dictionary<string, string>();
|
||||||
|
this.Cookies = new List<Cookie>();
|
||||||
|
|
||||||
|
this.Response = response;
|
||||||
|
this.ContentType = contentType;
|
||||||
|
this.StatusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResult(Stream responseStream, string contentType)
|
||||||
|
: this(null, contentType, HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
this.ResponseStream = responseStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResult(string responseText, string contentType)
|
||||||
|
: this(null, contentType, HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
this.ResponseText = responseText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResult(byte[] responseBytes, string contentType)
|
||||||
|
: this(null, contentType, HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
this.ResponseStream = new MemoryStream(responseBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ResponseText { get; private set; }
|
||||||
|
|
||||||
|
public Stream ResponseStream { get; private set; }
|
||||||
|
|
||||||
|
public string ContentType { get; set; }
|
||||||
|
|
||||||
|
public IDictionary<string, string> Headers { get; private set; }
|
||||||
|
|
||||||
|
public List<Cookie> Cookies { get; private set; }
|
||||||
|
|
||||||
|
public string ETag { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan? Age { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan? MaxAge { get; set; }
|
||||||
|
|
||||||
|
public DateTime? Expires { get; set; }
|
||||||
|
|
||||||
|
public DateTime? LastModified { get; set; }
|
||||||
|
|
||||||
|
public Func<IDisposable> ResultScope { get; set; }
|
||||||
|
|
||||||
|
public string Location
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (StatusCode == HttpStatusCode.OK)
|
||||||
|
StatusCode = HttpStatusCode.Redirect;
|
||||||
|
|
||||||
|
this.Headers["Location"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPermanentCookie(string name, string value)
|
||||||
|
{
|
||||||
|
SetCookie(name, value, DateTime.UtcNow.AddYears(20), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPermanentCookie(string name, string value, string path)
|
||||||
|
{
|
||||||
|
SetCookie(name, value, DateTime.UtcNow.AddYears(20), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSessionCookie(string name, string value)
|
||||||
|
{
|
||||||
|
SetSessionCookie(name, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSessionCookie(string name, string value, string path)
|
||||||
|
{
|
||||||
|
path = path ?? "/";
|
||||||
|
this.Headers["Set-Cookie"] = string.Format("{0}={1};path=" + path, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCookie(string name, string value, TimeSpan expiresIn, string path)
|
||||||
|
{
|
||||||
|
var expiresAt = DateTime.UtcNow.Add(expiresIn);
|
||||||
|
SetCookie(name, value, expiresAt, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCookie(string name, string value, DateTime expiresAt, string path, bool secure = false, bool httpOnly = false)
|
||||||
|
{
|
||||||
|
path = path ?? "/";
|
||||||
|
var cookie = string.Format("{0}={1};expires={2};path={3}", name, value, expiresAt.ToString("R"), path);
|
||||||
|
if (secure)
|
||||||
|
cookie += ";Secure";
|
||||||
|
if (httpOnly)
|
||||||
|
cookie += ";HttpOnly";
|
||||||
|
|
||||||
|
this.Headers["Set-Cookie"] = cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteCookie(string name)
|
||||||
|
{
|
||||||
|
var cookie = string.Format("{0}=;expires={1};path=/", name, DateTime.UtcNow.AddDays(-1).ToString("R"));
|
||||||
|
this.Headers["Set-Cookie"] = cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
public HttpStatusCode StatusCode
|
||||||
|
{
|
||||||
|
get { return (HttpStatusCode)Status; }
|
||||||
|
set { Status = (int)value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StatusDescription { get; set; }
|
||||||
|
|
||||||
|
public object Response { get; set; }
|
||||||
|
|
||||||
|
public MediaBrowser.Model.Services.IRequest RequestContext { get; set; }
|
||||||
|
|
||||||
|
public string View { get; set; }
|
||||||
|
|
||||||
|
public string Template { get; set; }
|
||||||
|
|
||||||
|
public int PaddingLength { get; set; }
|
||||||
|
|
||||||
|
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await WriteToInternalAsync(responseStream, cancellationToken).ConfigureAwait(false);
|
||||||
|
responseStream.Flush();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DisposeStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task WriteTo(Stream inStream, Stream outStream, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var memoryStream = inStream as MemoryStream;
|
||||||
|
if (memoryStream != null)
|
||||||
|
{
|
||||||
|
memoryStream.WriteTo(outStream);
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return inStream.CopyToAsync(outStream, 81920, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteToInternalAsync(Stream responseStream, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var response = RequestContext != null ? RequestContext.Response : null;
|
||||||
|
|
||||||
|
if (this.ResponseStream != null)
|
||||||
|
{
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
var ms = ResponseStream as MemoryStream;
|
||||||
|
if (ms != null)
|
||||||
|
{
|
||||||
|
response.SetContentLength(ms.Length);
|
||||||
|
|
||||||
|
await ms.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await WriteTo(this.ResponseStream, responseStream, cancellationToken).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.ResponseText != null)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(this.ResponseText);
|
||||||
|
if (response != null)
|
||||||
|
response.SetContentLength(bytes.Length);
|
||||||
|
|
||||||
|
await responseStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytesResponse = this.Response as byte[];
|
||||||
|
if (bytesResponse != null)
|
||||||
|
{
|
||||||
|
if (response != null)
|
||||||
|
response.SetContentLength(bytesResponse.Length);
|
||||||
|
|
||||||
|
await responseStream.WriteAsync(bytesResponse, 0, bytesResponse.Length).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentTypes.Instance.SerializeToStream(this.RequestContext, this.Response, responseStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeStream()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ResponseStream != null)
|
||||||
|
{
|
||||||
|
this.ResponseStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /*ignore*/ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
ServiceStack/HttpUtils.cs
Normal file
34
ServiceStack/HttpUtils.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//Copyright (c) Service Stack LLC. All Rights Reserved.
|
||||||
|
//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ServiceStack
|
||||||
|
{
|
||||||
|
internal static class HttpMethods
|
||||||
|
{
|
||||||
|
static readonly string[] allVerbs = new[] {
|
||||||
|
"OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616
|
||||||
|
"PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518
|
||||||
|
"VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT",
|
||||||
|
"MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY", // RFC 3253
|
||||||
|
"ORDERPATCH", // RFC 3648
|
||||||
|
"ACL", // RFC 3744
|
||||||
|
"PATCH", // https://datatracker.ietf.org/doc/draft-dusseault-http-patch/
|
||||||
|
"SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/
|
||||||
|
"BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY",
|
||||||
|
"POLL", "SUBSCRIBE", "UNSUBSCRIBE" //MS Exchange WebDav: http://msdn.microsoft.com/en-us/library/aa142917.aspx
|
||||||
|
};
|
||||||
|
|
||||||
|
public static HashSet<string> AllVerbs = new HashSet<string>(allVerbs);
|
||||||
|
|
||||||
|
public const string Get = "GET";
|
||||||
|
public const string Put = "PUT";
|
||||||
|
public const string Post = "POST";
|
||||||
|
public const string Delete = "DELETE";
|
||||||
|
public const string Options = "OPTIONS";
|
||||||
|
public const string Head = "HEAD";
|
||||||
|
public const string Patch = "PATCH";
|
||||||
|
}
|
||||||
|
}
|
25
ServiceStack/Properties/AssemblyInfo.cs
Normal file
25
ServiceStack/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
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("ServiceStack")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("Service Stack LLC")]
|
||||||
|
[assembly: AssemblyProduct("ServiceStack")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright (c) ServiceStack 2016")]
|
||||||
|
[assembly: AssemblyTrademark("Service Stack")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("06704d66-af8e-411f-8260-8d05de5ce6ad")]
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion("4.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("4.0.0.0")]
|
270
ServiceStack/ReflectionExtensions.cs
Normal file
270
ServiceStack/ReflectionExtensions.cs
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ServiceStack
|
||||||
|
{
|
||||||
|
public static class ReflectionExtensions
|
||||||
|
{
|
||||||
|
public static bool IsInstanceOf(this Type type, Type thisOrBaseType)
|
||||||
|
{
|
||||||
|
while (type != null)
|
||||||
|
{
|
||||||
|
if (type == thisOrBaseType)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
type = type.BaseType();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type FirstGenericType(this Type type)
|
||||||
|
{
|
||||||
|
while (type != null)
|
||||||
|
{
|
||||||
|
if (type.IsGeneric())
|
||||||
|
return type;
|
||||||
|
|
||||||
|
type = type.BaseType();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type GetTypeWithGenericTypeDefinitionOf(this Type type, Type genericTypeDefinition)
|
||||||
|
{
|
||||||
|
foreach (var t in type.GetTypeInterfaces())
|
||||||
|
{
|
||||||
|
if (t.IsGeneric() && t.GetGenericTypeDefinition() == genericTypeDefinition)
|
||||||
|
{
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var genericType = type.FirstGenericType();
|
||||||
|
if (genericType != null && genericType.GetGenericTypeDefinition() == genericTypeDefinition)
|
||||||
|
{
|
||||||
|
return genericType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PropertyInfo[] GetAllProperties(this Type type)
|
||||||
|
{
|
||||||
|
if (type.IsInterface())
|
||||||
|
{
|
||||||
|
var propertyInfos = new List<PropertyInfo>();
|
||||||
|
|
||||||
|
var considered = new List<Type>();
|
||||||
|
var queue = new Queue<Type>();
|
||||||
|
considered.Add(type);
|
||||||
|
queue.Enqueue(type);
|
||||||
|
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
var subType = queue.Dequeue();
|
||||||
|
foreach (var subInterface in subType.GetTypeInterfaces())
|
||||||
|
{
|
||||||
|
if (considered.Contains(subInterface)) continue;
|
||||||
|
|
||||||
|
considered.Add(subInterface);
|
||||||
|
queue.Enqueue(subInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeProperties = subType.GetTypesProperties();
|
||||||
|
|
||||||
|
var newPropertyInfos = typeProperties
|
||||||
|
.Where(x => !propertyInfos.Contains(x));
|
||||||
|
|
||||||
|
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyInfos.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.GetTypesProperties()
|
||||||
|
.Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PropertyInfo[] GetPublicProperties(this Type type)
|
||||||
|
{
|
||||||
|
if (type.IsInterface())
|
||||||
|
{
|
||||||
|
var propertyInfos = new List<PropertyInfo>();
|
||||||
|
|
||||||
|
var considered = new List<Type>();
|
||||||
|
var queue = new Queue<Type>();
|
||||||
|
considered.Add(type);
|
||||||
|
queue.Enqueue(type);
|
||||||
|
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
var subType = queue.Dequeue();
|
||||||
|
foreach (var subInterface in subType.GetTypeInterfaces())
|
||||||
|
{
|
||||||
|
if (considered.Contains(subInterface)) continue;
|
||||||
|
|
||||||
|
considered.Add(subInterface);
|
||||||
|
queue.Enqueue(subInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeProperties = subType.GetTypesPublicProperties();
|
||||||
|
|
||||||
|
var newPropertyInfos = typeProperties
|
||||||
|
.Where(x => !propertyInfos.Contains(x));
|
||||||
|
|
||||||
|
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyInfos.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.GetTypesPublicProperties()
|
||||||
|
.Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public const string DataMember = "DataMemberAttribute";
|
||||||
|
|
||||||
|
internal static string[] IgnoreAttributesNamed = new[] {
|
||||||
|
"IgnoreDataMemberAttribute",
|
||||||
|
"JsonIgnoreAttribute"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static PropertyInfo[] GetSerializableProperties(this Type type)
|
||||||
|
{
|
||||||
|
var properties = type.IsDto()
|
||||||
|
? type.GetAllProperties()
|
||||||
|
: type.GetPublicProperties();
|
||||||
|
return properties.OnlySerializableProperties(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static List<Type> _excludeTypes = new List<Type> { typeof(Stream) };
|
||||||
|
|
||||||
|
public static PropertyInfo[] OnlySerializableProperties(this PropertyInfo[] properties, Type type = null)
|
||||||
|
{
|
||||||
|
var isDto = type.IsDto();
|
||||||
|
var readableProperties = properties.Where(x => x.PropertyGetMethod(nonPublic: isDto) != null);
|
||||||
|
|
||||||
|
if (isDto)
|
||||||
|
{
|
||||||
|
return readableProperties.Where(attr =>
|
||||||
|
attr.HasAttribute<DataMemberAttribute>()).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// else return those properties that are not decorated with IgnoreDataMember
|
||||||
|
return readableProperties
|
||||||
|
.Where(prop => prop.AllAttributes()
|
||||||
|
.All(attr =>
|
||||||
|
{
|
||||||
|
var name = attr.GetType().Name;
|
||||||
|
return !IgnoreAttributesNamed.Contains(name);
|
||||||
|
}))
|
||||||
|
.Where(prop => !_excludeTypes.Contains(prop.PropertyType))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PlatformExtensions //Because WinRT is a POS
|
||||||
|
{
|
||||||
|
public static bool IsInterface(this Type type)
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().IsInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsGeneric(this Type type)
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().IsGenericType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type BaseType(this Type type)
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().BaseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type[] GetTypeInterfaces(this Type type)
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().ImplementedInterfaces.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static PropertyInfo[] GetTypesPublicProperties(this Type subType)
|
||||||
|
{
|
||||||
|
var pis = new List<PropertyInfo>();
|
||||||
|
foreach (var pi in subType.GetRuntimeProperties())
|
||||||
|
{
|
||||||
|
var mi = pi.GetMethod ?? pi.SetMethod;
|
||||||
|
if (mi != null && mi.IsStatic) continue;
|
||||||
|
pis.Add(pi);
|
||||||
|
}
|
||||||
|
return pis.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static PropertyInfo[] GetTypesProperties(this Type subType)
|
||||||
|
{
|
||||||
|
var pis = new List<PropertyInfo>();
|
||||||
|
foreach (var pi in subType.GetRuntimeProperties())
|
||||||
|
{
|
||||||
|
var mi = pi.GetMethod ?? pi.SetMethod;
|
||||||
|
if (mi != null && mi.IsStatic) continue;
|
||||||
|
pis.Add(pi);
|
||||||
|
}
|
||||||
|
return pis.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HasAttribute<T>(this Type type)
|
||||||
|
{
|
||||||
|
return type.AllAttributes().Any(x => x.GetType() == typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HasAttribute<T>(this PropertyInfo pi)
|
||||||
|
{
|
||||||
|
return pi.AllAttributes().Any(x => x.GetType() == typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsDto(this Type type)
|
||||||
|
{
|
||||||
|
if (type == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return type.HasAttribute<DataContractAttribute>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodInfo PropertyGetMethod(this PropertyInfo pi, bool nonPublic = false)
|
||||||
|
{
|
||||||
|
return pi.GetMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object[] AllAttributes(this PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
return propertyInfo.GetCustomAttributes(true).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object[] AllAttributes(this PropertyInfo propertyInfo, Type attrType)
|
||||||
|
{
|
||||||
|
return propertyInfo.GetCustomAttributes(true).Where(x => attrType.IsInstanceOf(x.GetType())).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object[] AllAttributes(this Type type)
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().GetCustomAttributes(true).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TAttr[] AllAttributes<TAttr>(this PropertyInfo pi)
|
||||||
|
{
|
||||||
|
return pi.AllAttributes(typeof(TAttr)).Cast<TAttr>().ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TAttr[] AllAttributes<TAttr>(this Type type)
|
||||||
|
where TAttr : Attribute
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().GetCustomAttributes<TAttr>(true).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
ServiceStack/ServiceStack.csproj
Normal file
131
ServiceStack/ServiceStack.csproj
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProductVersion>9.0.30729</ProductVersion>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<ProjectGuid>{680A1709-25EB-4D52-A87F-EE03FFD94BAA}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>ServiceStack</RootNamespace>
|
||||||
|
<AssemblyName>ServiceStack</AssemblyName>
|
||||||
|
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||||
|
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
|
||||||
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<FileUpgradeFlags>
|
||||||
|
</FileUpgradeFlags>
|
||||||
|
<OldToolsVersion>3.5</OldToolsVersion>
|
||||||
|
<UpgradeBackupLocation />
|
||||||
|
<PublishUrl>publish\</PublishUrl>
|
||||||
|
<Install>true</Install>
|
||||||
|
<InstallFrom>Disk</InstallFrom>
|
||||||
|
<UpdateEnabled>false</UpdateEnabled>
|
||||||
|
<UpdateMode>Foreground</UpdateMode>
|
||||||
|
<UpdateInterval>7</UpdateInterval>
|
||||||
|
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||||
|
<UpdatePeriodically>false</UpdatePeriodically>
|
||||||
|
<UpdateRequired>false</UpdateRequired>
|
||||||
|
<MapFileExtensions>true</MapFileExtensions>
|
||||||
|
<ApplicationRevision>0</ApplicationRevision>
|
||||||
|
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||||
|
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||||
|
<UseApplicationTrust>false</UseApplicationTrust>
|
||||||
|
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>True</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>False</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>TRACE;DEBUG;MONO</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
</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>
|
||||||
|
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<DocumentationFile>
|
||||||
|
</DocumentationFile>
|
||||||
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Signed|AnyCPU'">
|
||||||
|
<OutputPath>bin\Signed\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<DocumentationFile>bin\Release\ServiceStack.XML</DocumentationFile>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="HttpUtils.cs" />
|
||||||
|
<Compile Include="Host\ContentTypes.cs" />
|
||||||
|
<Compile Include="ReflectionExtensions.cs" />
|
||||||
|
<Compile Include="StringMapTypeDeserializer.cs" />
|
||||||
|
<Compile Include="Host\HttpResponseStreamWrapper.cs" />
|
||||||
|
<Compile Include="HttpResult.cs" />
|
||||||
|
<Compile Include="ServiceStackHost.cs" />
|
||||||
|
<Compile Include="ServiceStackHost.Runtime.cs" />
|
||||||
|
<Compile Include="Host\ServiceExec.cs" />
|
||||||
|
<Compile Include="UrlExtensions.cs" />
|
||||||
|
<Compile Include="Host\ActionContext.cs" />
|
||||||
|
<Compile Include="HttpRequestExtensions.cs" />
|
||||||
|
<Compile Include="Host\RestPath.cs" />
|
||||||
|
<Compile Include="Host\ServiceController.cs" />
|
||||||
|
<Compile Include="Host\ServiceMetadata.cs" />
|
||||||
|
<Compile Include="Host\RestHandler.cs" />
|
||||||
|
<Compile Include="HttpResponseExtensionsInternal.cs" />
|
||||||
|
<Compile Include="HttpHandlerFactory.cs" />
|
||||||
|
<Compile Include="FilterAttributeCache.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||||
|
<Visible>False</Visible>
|
||||||
|
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
|
||||||
|
<Install>false</Install>
|
||||||
|
</BootstrapperPackage>
|
||||||
|
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||||
|
<Visible>False</Visible>
|
||||||
|
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||||
|
<Install>true</Install>
|
||||||
|
</BootstrapperPackage>
|
||||||
|
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
|
||||||
|
<Visible>False</Visible>
|
||||||
|
<ProductName>Windows Installer 3.1</ProductName>
|
||||||
|
<Install>true</Install>
|
||||||
|
</BootstrapperPackage>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||||
|
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
|
||||||
|
<Name>MediaBrowser.Common</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
|
||||||
|
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
|
||||||
|
<Name>MediaBrowser.Model</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
19
ServiceStack/ServiceStack.xproj
Normal file
19
ServiceStack/ServiceStack.xproj
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>b2d733ab-620e-4c53-88a4-4b6638ab6a7a</ProjectGuid>
|
||||||
|
<RootNamespace>ServiceStack</RootNamespace>
|
||||||
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||||
|
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
74
ServiceStack/ServiceStackHost.Runtime.cs
Normal file
74
ServiceStack/ServiceStackHost.Runtime.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright (c) Service Stack LLC. All Rights Reserved.
|
||||||
|
// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt
|
||||||
|
|
||||||
|
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
using ServiceStack.Support.WebHost;
|
||||||
|
|
||||||
|
namespace ServiceStack
|
||||||
|
{
|
||||||
|
public abstract partial class ServiceStackHost
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the request filters. Returns whether or not the request has been handled
|
||||||
|
/// and no more processing should be done.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual bool ApplyRequestFilters(IRequest req, IResponse res, object requestDto)
|
||||||
|
{
|
||||||
|
if (res.IsClosed) return res.IsClosed;
|
||||||
|
|
||||||
|
//Exec all RequestFilter attributes with Priority < 0
|
||||||
|
var attributes = FilterAttributeCache.GetRequestFilterAttributes(requestDto.GetType());
|
||||||
|
var i = 0;
|
||||||
|
for (; i < attributes.Length && attributes[i].Priority < 0; i++)
|
||||||
|
{
|
||||||
|
var attribute = attributes[i];
|
||||||
|
attribute.RequestFilter(req, res, requestDto);
|
||||||
|
if (res.IsClosed) return res.IsClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.IsClosed) return res.IsClosed;
|
||||||
|
|
||||||
|
//Exec global filters
|
||||||
|
foreach (var requestFilter in GlobalRequestFilters)
|
||||||
|
{
|
||||||
|
requestFilter(req, res, requestDto);
|
||||||
|
if (res.IsClosed) return res.IsClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Exec remaining RequestFilter attributes with Priority >= 0
|
||||||
|
for (; i < attributes.Length && attributes[i].Priority >= 0; i++)
|
||||||
|
{
|
||||||
|
var attribute = attributes[i];
|
||||||
|
attribute.RequestFilter(req, res, requestDto);
|
||||||
|
if (res.IsClosed) return res.IsClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.IsClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the response filters. Returns whether or not the request has been handled
|
||||||
|
/// and no more processing should be done.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual bool ApplyResponseFilters(IRequest req, IResponse res, object response)
|
||||||
|
{
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
if (res.IsClosed) return res.IsClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Exec global filters
|
||||||
|
foreach (var responseFilter in GlobalResponseFilters)
|
||||||
|
{
|
||||||
|
responseFilter(req, res, response);
|
||||||
|
if (res.IsClosed) return res.IsClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.IsClosed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
104
ServiceStack/ServiceStackHost.cs
Normal file
104
ServiceStack/ServiceStackHost.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// Copyright (c) Service Stack LLC. All Rights Reserved.
|
||||||
|
// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
using ServiceStack.Host;
|
||||||
|
|
||||||
|
namespace ServiceStack
|
||||||
|
{
|
||||||
|
public abstract partial class ServiceStackHost : IDisposable
|
||||||
|
{
|
||||||
|
public static ServiceStackHost Instance { get; protected set; }
|
||||||
|
|
||||||
|
protected ServiceStackHost(string serviceName)
|
||||||
|
{
|
||||||
|
ServiceName = serviceName;
|
||||||
|
ServiceController = CreateServiceController();
|
||||||
|
|
||||||
|
RestPaths = new List<RestPath>();
|
||||||
|
Metadata = new ServiceMetadata();
|
||||||
|
GlobalRequestFilters = new List<Action<IRequest, IResponse, object>>();
|
||||||
|
GlobalResponseFilters = new List<Action<IRequest, IResponse, object>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void Configure();
|
||||||
|
|
||||||
|
public abstract object CreateInstance(Type type);
|
||||||
|
|
||||||
|
protected abstract ServiceController CreateServiceController();
|
||||||
|
|
||||||
|
public virtual ServiceStackHost Init()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
ServiceController.Init();
|
||||||
|
Configure();
|
||||||
|
|
||||||
|
ServiceController.AfterInit();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ServiceStackHost Start(string urlBase)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Start(listeningAtUrlBase) is not supported by this AppHost");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ServiceName { get; set; }
|
||||||
|
|
||||||
|
public ServiceMetadata Metadata { get; set; }
|
||||||
|
|
||||||
|
public ServiceController ServiceController { get; set; }
|
||||||
|
|
||||||
|
public List<RestPath> RestPaths = new List<RestPath>();
|
||||||
|
|
||||||
|
public List<Action<IRequest, IResponse, object>> GlobalRequestFilters { get; set; }
|
||||||
|
|
||||||
|
public List<Action<IRequest, IResponse, object>> GlobalResponseFilters { get; set; }
|
||||||
|
|
||||||
|
public abstract T TryResolve<T>();
|
||||||
|
public abstract T Resolve<T>();
|
||||||
|
|
||||||
|
public virtual MediaBrowser.Model.Services.RouteAttribute[] GetRouteAttributes(Type requestType)
|
||||||
|
{
|
||||||
|
return requestType.AllAttributes<MediaBrowser.Model.Services.RouteAttribute>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract object GetTaskResult(Task task, string requestName);
|
||||||
|
|
||||||
|
public abstract Func<string, object> GetParseFn(Type propertyType);
|
||||||
|
|
||||||
|
public abstract void SerializeToJson(object o, Stream stream);
|
||||||
|
public abstract void SerializeToXml(object o, Stream stream);
|
||||||
|
public abstract object DeserializeXml(Type type, Stream stream);
|
||||||
|
public abstract object DeserializeJson(Type type, Stream stream);
|
||||||
|
|
||||||
|
public virtual void Dispose()
|
||||||
|
{
|
||||||
|
//JsConfig.Reset(); //Clears Runtime Attributes
|
||||||
|
|
||||||
|
Instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract ILogger Logger
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnLogError(Type type, string message)
|
||||||
|
{
|
||||||
|
Logger.Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnLogError(Type type, string message, Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException(message, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
126
ServiceStack/StringMapTypeDeserializer.cs
Normal file
126
ServiceStack/StringMapTypeDeserializer.cs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace ServiceStack.Serialization
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Serializer cache of delegates required to create a type from a string map (e.g. for REST urls)
|
||||||
|
/// </summary>
|
||||||
|
public class StringMapTypeDeserializer
|
||||||
|
{
|
||||||
|
internal class PropertySerializerEntry
|
||||||
|
{
|
||||||
|
public PropertySerializerEntry(Action<object,object> propertySetFn, Func<string, object> propertyParseStringFn)
|
||||||
|
{
|
||||||
|
PropertySetFn = propertySetFn;
|
||||||
|
PropertyParseStringFn = propertyParseStringFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action<object, object> PropertySetFn;
|
||||||
|
public Func<string,object> PropertyParseStringFn;
|
||||||
|
public Type PropertyType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Type type;
|
||||||
|
private readonly Dictionary<string, PropertySerializerEntry> propertySetterMap
|
||||||
|
= new Dictionary<string, PropertySerializerEntry>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public Func<string, object> GetParseFn(Type propertyType)
|
||||||
|
{
|
||||||
|
//Don't JSV-decode string values for string properties
|
||||||
|
if (propertyType == typeof(string))
|
||||||
|
return s => s;
|
||||||
|
|
||||||
|
return ServiceStackHost.Instance.GetParseFn(propertyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringMapTypeDeserializer(Type type)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
|
||||||
|
foreach (var propertyInfo in type.GetSerializableProperties())
|
||||||
|
{
|
||||||
|
var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo);
|
||||||
|
var propertyType = propertyInfo.PropertyType;
|
||||||
|
var propertyParseStringFn = GetParseFn(propertyType);
|
||||||
|
var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType };
|
||||||
|
|
||||||
|
var attr = propertyInfo.AllAttributes<DataMemberAttribute>().FirstOrDefault();
|
||||||
|
if (attr != null && attr.Name != null)
|
||||||
|
{
|
||||||
|
propertySetterMap[attr.Name] = propertySerializer;
|
||||||
|
}
|
||||||
|
propertySetterMap[propertyInfo.Name] = propertySerializer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object PopulateFromMap(object instance, IDictionary<string, string> keyValuePairs)
|
||||||
|
{
|
||||||
|
string propertyName = null;
|
||||||
|
string propertyTextValue = null;
|
||||||
|
PropertySerializerEntry propertySerializerEntry = null;
|
||||||
|
|
||||||
|
if (instance == null)
|
||||||
|
instance = ServiceStackHost.Instance.CreateInstance(type);
|
||||||
|
|
||||||
|
foreach (var pair in keyValuePairs.Where(x => !string.IsNullOrEmpty(x.Value)))
|
||||||
|
{
|
||||||
|
propertyName = pair.Key;
|
||||||
|
propertyTextValue = pair.Value;
|
||||||
|
|
||||||
|
if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry))
|
||||||
|
{
|
||||||
|
if (propertyName == "v")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertySerializerEntry.PropertySetFn == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertySerializerEntry.PropertyType == typeof(bool))
|
||||||
|
{
|
||||||
|
//InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value
|
||||||
|
propertyTextValue = LeftPart(propertyTextValue, ',');
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = propertySerializerEntry.PropertyParseStringFn(propertyTextValue);
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
propertySerializerEntry.PropertySetFn(instance, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LeftPart(string strVal, char needle)
|
||||||
|
{
|
||||||
|
if (strVal == null) return null;
|
||||||
|
var pos = strVal.IndexOf(needle);
|
||||||
|
return pos == -1
|
||||||
|
? strVal
|
||||||
|
: strVal.Substring(0, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class TypeAccessor
|
||||||
|
{
|
||||||
|
public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Any()) return null;
|
||||||
|
|
||||||
|
var setMethodInfo = propertyInfo.SetMethod;
|
||||||
|
return (instance, value) => setMethodInfo.Invoke(instance, new[] { value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
ServiceStack/UrlExtensions.cs
Normal file
33
ServiceStack/UrlExtensions.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ServiceStack
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Donated by Ivan Korneliuk from his post:
|
||||||
|
/// http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html
|
||||||
|
///
|
||||||
|
/// Modified to only allow using routes matching the supplied HTTP Verb
|
||||||
|
/// </summary>
|
||||||
|
public static class UrlExtensions
|
||||||
|
{
|
||||||
|
public static string GetOperationName(this Type type)
|
||||||
|
{
|
||||||
|
var typeName = type.FullName != null //can be null, e.g. generic types
|
||||||
|
? LeftPart(type.FullName, "[[") //Generic Fullname
|
||||||
|
.Replace(type.Namespace + ".", "") //Trim Namespaces
|
||||||
|
.Replace("+", ".") //Convert nested into normal type
|
||||||
|
: type.Name;
|
||||||
|
|
||||||
|
return type.IsGenericParameter ? "'" + typeName : typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LeftPart(string strVal, string needle)
|
||||||
|
{
|
||||||
|
if (strVal == null) return null;
|
||||||
|
var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase);
|
||||||
|
return pos == -1
|
||||||
|
? strVal
|
||||||
|
: strVal.Substring(0, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
ServiceStack/packages.config
Normal file
3
ServiceStack/packages.config
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
</packages>
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"frameworks":{
|
"frameworks":{
|
||||||
"netstandard1.6":{
|
"netstandard1.6":{
|
||||||
"dependencies":{
|
"dependencies":{
|
17
SocketHttpListener.Portable/ByteOrder.cs
Normal file
17
SocketHttpListener.Portable/ByteOrder.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the values that indicate whether the byte order is a Little-endian or Big-endian.
|
||||||
|
/// </summary>
|
||||||
|
public enum ByteOrder : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates a Little-endian.
|
||||||
|
/// </summary>
|
||||||
|
Little,
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates a Big-endian.
|
||||||
|
/// </summary>
|
||||||
|
Big
|
||||||
|
}
|
||||||
|
}
|
90
SocketHttpListener.Portable/CloseEventArgs.cs
Normal file
90
SocketHttpListener.Portable/CloseEventArgs.cs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the event data associated with a <see cref="WebSocket.OnClose"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A <see cref="WebSocket.OnClose"/> event occurs when the WebSocket connection has been closed.
|
||||||
|
/// If you would like to get the reason for the close, you should access the <see cref="Code"/> or
|
||||||
|
/// <see cref="Reason"/> property.
|
||||||
|
/// </remarks>
|
||||||
|
public class CloseEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private bool _clean;
|
||||||
|
private ushort _code;
|
||||||
|
private string _reason;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Constructors
|
||||||
|
|
||||||
|
internal CloseEventArgs (PayloadData payload)
|
||||||
|
{
|
||||||
|
var data = payload.ApplicationData;
|
||||||
|
var len = data.Length;
|
||||||
|
_code = len > 1
|
||||||
|
? data.SubArray (0, 2).ToUInt16 (ByteOrder.Big)
|
||||||
|
: (ushort) CloseStatusCode.NoStatusCode;
|
||||||
|
|
||||||
|
_reason = len > 2
|
||||||
|
? GetUtf8String(data.SubArray (2, len - 2))
|
||||||
|
: String.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetUtf8String(byte[] bytes)
|
||||||
|
{
|
||||||
|
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the status code for the close.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="ushort"/> that represents the status code for the close if any.
|
||||||
|
/// </value>
|
||||||
|
public ushort Code {
|
||||||
|
get {
|
||||||
|
return _code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the reason for the close.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that represents the reason for the close if any.
|
||||||
|
/// </value>
|
||||||
|
public string Reason {
|
||||||
|
get {
|
||||||
|
return _reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the WebSocket connection has been closed cleanly.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if the WebSocket connection has been closed cleanly; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public bool WasClean {
|
||||||
|
get {
|
||||||
|
return _clean;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal set {
|
||||||
|
_clean = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
94
SocketHttpListener.Portable/CloseStatusCode.cs
Normal file
94
SocketHttpListener.Portable/CloseStatusCode.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the values of the status code for the WebSocket connection close.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// The values of the status code are defined in
|
||||||
|
/// <see href="http://tools.ietf.org/html/rfc6455#section-7.4">Section 7.4</see>
|
||||||
|
/// of RFC 6455.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// "Reserved value" must not be set as a status code in a close control frame
|
||||||
|
/// by an endpoint. It's designated for use in applications expecting a status
|
||||||
|
/// code to indicate that the connection was closed due to the system grounds.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public enum CloseStatusCode : ushort
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1000.
|
||||||
|
/// Indicates a normal close.
|
||||||
|
/// </summary>
|
||||||
|
Normal = 1000,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1001.
|
||||||
|
/// Indicates that an endpoint is going away.
|
||||||
|
/// </summary>
|
||||||
|
Away = 1001,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1002.
|
||||||
|
/// Indicates that an endpoint is terminating the connection due to a protocol error.
|
||||||
|
/// </summary>
|
||||||
|
ProtocolError = 1002,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1003.
|
||||||
|
/// Indicates that an endpoint is terminating the connection because it has received
|
||||||
|
/// an unacceptable type message.
|
||||||
|
/// </summary>
|
||||||
|
IncorrectData = 1003,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1004.
|
||||||
|
/// Still undefined. Reserved value.
|
||||||
|
/// </summary>
|
||||||
|
Undefined = 1004,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1005.
|
||||||
|
/// Indicates that no status code was actually present. Reserved value.
|
||||||
|
/// </summary>
|
||||||
|
NoStatusCode = 1005,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1006.
|
||||||
|
/// Indicates that the connection was closed abnormally. Reserved value.
|
||||||
|
/// </summary>
|
||||||
|
Abnormal = 1006,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1007.
|
||||||
|
/// Indicates that an endpoint is terminating the connection because it has received
|
||||||
|
/// a message that contains a data that isn't consistent with the type of the message.
|
||||||
|
/// </summary>
|
||||||
|
InconsistentData = 1007,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1008.
|
||||||
|
/// Indicates that an endpoint is terminating the connection because it has received
|
||||||
|
/// a message that violates its policy.
|
||||||
|
/// </summary>
|
||||||
|
PolicyViolation = 1008,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1009.
|
||||||
|
/// Indicates that an endpoint is terminating the connection because it has received
|
||||||
|
/// a message that is too big to process.
|
||||||
|
/// </summary>
|
||||||
|
TooBig = 1009,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1010.
|
||||||
|
/// Indicates that the client is terminating the connection because it has expected
|
||||||
|
/// the server to negotiate one or more extension, but the server didn't return them
|
||||||
|
/// in the handshake response.
|
||||||
|
/// </summary>
|
||||||
|
IgnoreExtension = 1010,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1011.
|
||||||
|
/// Indicates that the server is terminating the connection because it has encountered
|
||||||
|
/// an unexpected condition that prevented it from fulfilling the request.
|
||||||
|
/// </summary>
|
||||||
|
ServerError = 1011,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to close status 1015.
|
||||||
|
/// Indicates that the connection was closed due to a failure to perform a TLS handshake.
|
||||||
|
/// Reserved value.
|
||||||
|
/// </summary>
|
||||||
|
TlsHandshakeFailure = 1015
|
||||||
|
}
|
||||||
|
}
|
23
SocketHttpListener.Portable/CompressionMethod.cs
Normal file
23
SocketHttpListener.Portable/CompressionMethod.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the values of the compression method used to compress the message on the WebSocket
|
||||||
|
/// connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The values of the compression method are defined in
|
||||||
|
/// <see href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-09">Compression
|
||||||
|
/// Extensions for WebSocket</see>.
|
||||||
|
/// </remarks>
|
||||||
|
public enum CompressionMethod : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates non compression.
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates using DEFLATE.
|
||||||
|
/// </summary>
|
||||||
|
Deflate
|
||||||
|
}
|
||||||
|
}
|
46
SocketHttpListener.Portable/ErrorEventArgs.cs
Normal file
46
SocketHttpListener.Portable/ErrorEventArgs.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the event data associated with a <see cref="WebSocket.OnError"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A <see cref="WebSocket.OnError"/> event occurs when the <see cref="WebSocket"/> gets an error.
|
||||||
|
/// If you would like to get the error message, you should access the <see cref="Message"/>
|
||||||
|
/// property.
|
||||||
|
/// </remarks>
|
||||||
|
public class ErrorEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private string _message;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Constructors
|
||||||
|
|
||||||
|
internal ErrorEventArgs (string message)
|
||||||
|
{
|
||||||
|
_message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that represents the error message.
|
||||||
|
/// </value>
|
||||||
|
public string Message {
|
||||||
|
get {
|
||||||
|
return _message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
1089
SocketHttpListener.Portable/Ext.cs
Normal file
1089
SocketHttpListener.Portable/Ext.cs
Normal file
File diff suppressed because it is too large
Load Diff
8
SocketHttpListener.Portable/Fin.cs
Normal file
8
SocketHttpListener.Portable/Fin.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
internal enum Fin : byte
|
||||||
|
{
|
||||||
|
More = 0x0,
|
||||||
|
Final = 0x1
|
||||||
|
}
|
||||||
|
}
|
104
SocketHttpListener.Portable/HttpBase.cs
Normal file
104
SocketHttpListener.Portable/HttpBase.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
|
||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
internal abstract class HttpBase
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private QueryParamCollection _headers;
|
||||||
|
private Version _version;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Fields
|
||||||
|
|
||||||
|
internal byte[] EntityBodyData;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Protected Fields
|
||||||
|
|
||||||
|
protected const string CrLf = "\r\n";
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Protected Constructors
|
||||||
|
|
||||||
|
protected HttpBase(Version version, QueryParamCollection headers)
|
||||||
|
{
|
||||||
|
_version = version;
|
||||||
|
_headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
public string EntityBody
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var data = EntityBodyData;
|
||||||
|
|
||||||
|
return data != null && data.Length > 0
|
||||||
|
? getEncoding(_headers["Content-Type"]).GetString(data, 0, data.Length)
|
||||||
|
: String.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryParamCollection Headers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Version ProtocolVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private static Encoding getEncoding(string contentType)
|
||||||
|
{
|
||||||
|
if (contentType == null || contentType.Length == 0)
|
||||||
|
return Encoding.UTF8;
|
||||||
|
|
||||||
|
var i = contentType.IndexOf("charset=", StringComparison.Ordinal);
|
||||||
|
if (i == -1)
|
||||||
|
return Encoding.UTF8;
|
||||||
|
|
||||||
|
var charset = contentType.Substring(i + 8);
|
||||||
|
i = charset.IndexOf(';');
|
||||||
|
if (i != -1)
|
||||||
|
charset = charset.Substring(0, i).TrimEnd();
|
||||||
|
|
||||||
|
return Encoding.GetEncoding(charset.Trim('"'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
public byte[] ToByteArray()
|
||||||
|
{
|
||||||
|
return Encoding.UTF8.GetBytes(ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
161
SocketHttpListener.Portable/HttpResponse.cs
Normal file
161
SocketHttpListener.Portable/HttpResponse.cs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
|
||||||
|
using HttpVersion = SocketHttpListener.Net.HttpVersion;
|
||||||
|
using System.Linq;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
|
||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
internal class HttpResponse : HttpBase
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private string _code;
|
||||||
|
private string _reason;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Constructors
|
||||||
|
|
||||||
|
private HttpResponse(string code, string reason, Version version, QueryParamCollection headers)
|
||||||
|
: base(version, headers)
|
||||||
|
{
|
||||||
|
_code = code;
|
||||||
|
_reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Constructors
|
||||||
|
|
||||||
|
internal HttpResponse(HttpStatusCode code)
|
||||||
|
: this(code, code.GetDescription())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal HttpResponse(HttpStatusCode code, string reason)
|
||||||
|
: this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection())
|
||||||
|
{
|
||||||
|
Headers["Server"] = "websocket-sharp/1.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
public CookieCollection Cookies
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Headers.GetCookies(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsProxyAuthenticationRequired
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _code == "407";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsUnauthorized
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _code == "401";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsWebSocketResponse
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var headers = Headers;
|
||||||
|
return ProtocolVersion > HttpVersion.Version10 &&
|
||||||
|
_code == "101" &&
|
||||||
|
headers.Contains("Upgrade", "websocket") &&
|
||||||
|
headers.Contains("Connection", "Upgrade");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Reason
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StatusCode
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Methods
|
||||||
|
|
||||||
|
internal static HttpResponse CreateCloseResponse(HttpStatusCode code)
|
||||||
|
{
|
||||||
|
var res = new HttpResponse(code);
|
||||||
|
res.Headers["Connection"] = "close";
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static HttpResponse CreateWebSocketResponse()
|
||||||
|
{
|
||||||
|
var res = new HttpResponse(HttpStatusCode.SwitchingProtocols);
|
||||||
|
|
||||||
|
var headers = res.Headers;
|
||||||
|
headers["Upgrade"] = "websocket";
|
||||||
|
headers["Connection"] = "Upgrade";
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
public void SetCookies(CookieCollection cookies)
|
||||||
|
{
|
||||||
|
if (cookies == null || cookies.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var headers = Headers;
|
||||||
|
var sorted = cookies.OfType<Cookie>().OrderBy(i => i.Name).ToList();
|
||||||
|
|
||||||
|
foreach (var cookie in sorted)
|
||||||
|
headers.Add("Set-Cookie", cookie.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var output = new StringBuilder(64);
|
||||||
|
output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf);
|
||||||
|
|
||||||
|
var headers = Headers;
|
||||||
|
foreach (var key in headers.Keys)
|
||||||
|
output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf);
|
||||||
|
|
||||||
|
output.Append(CrLf);
|
||||||
|
|
||||||
|
var entity = EntityBody;
|
||||||
|
if (entity.Length > 0)
|
||||||
|
output.Append(entity);
|
||||||
|
|
||||||
|
return output.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
8
SocketHttpListener.Portable/Mask.cs
Normal file
8
SocketHttpListener.Portable/Mask.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
internal enum Mask : byte
|
||||||
|
{
|
||||||
|
Unmask = 0x0,
|
||||||
|
Mask = 0x1
|
||||||
|
}
|
||||||
|
}
|
96
SocketHttpListener.Portable/MessageEventArgs.cs
Normal file
96
SocketHttpListener.Portable/MessageEventArgs.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the event data associated with a <see cref="WebSocket.OnMessage"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A <see cref="WebSocket.OnMessage"/> event occurs when the <see cref="WebSocket"/> receives
|
||||||
|
/// a text or binary data frame.
|
||||||
|
/// If you want to get the received data, you access the <see cref="MessageEventArgs.Data"/> or
|
||||||
|
/// <see cref="MessageEventArgs.RawData"/> property.
|
||||||
|
/// </remarks>
|
||||||
|
public class MessageEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private string _data;
|
||||||
|
private Opcode _opcode;
|
||||||
|
private byte[] _rawData;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Constructors
|
||||||
|
|
||||||
|
internal MessageEventArgs (Opcode opcode, byte[] data)
|
||||||
|
{
|
||||||
|
_opcode = opcode;
|
||||||
|
_rawData = data;
|
||||||
|
_data = convertToString (opcode, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal MessageEventArgs (Opcode opcode, PayloadData payload)
|
||||||
|
{
|
||||||
|
_opcode = opcode;
|
||||||
|
_rawData = payload.ApplicationData;
|
||||||
|
_data = convertToString (opcode, _rawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the received data as a <see cref="string"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that contains the received data.
|
||||||
|
/// </value>
|
||||||
|
public string Data {
|
||||||
|
get {
|
||||||
|
return _data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the received data as an array of <see cref="byte"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// An array of <see cref="byte"/> that contains the received data.
|
||||||
|
/// </value>
|
||||||
|
public byte [] RawData {
|
||||||
|
get {
|
||||||
|
return _rawData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the received data.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// One of the <see cref="Opcode"/> values, indicates the type of the received data.
|
||||||
|
/// </value>
|
||||||
|
public Opcode Type {
|
||||||
|
get {
|
||||||
|
return _opcode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private static string convertToString (Opcode opcode, byte [] data)
|
||||||
|
{
|
||||||
|
return data.Length == 0
|
||||||
|
? String.Empty
|
||||||
|
: opcode == Opcode.Text
|
||||||
|
? Encoding.UTF8.GetString (data, 0, data.Length)
|
||||||
|
: opcode.ToString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
public delegate AuthenticationSchemes AuthenticationSchemeSelector(HttpListenerRequest httpRequest);
|
||||||
|
}
|
371
SocketHttpListener.Portable/Net/ChunkStream.cs
Normal file
371
SocketHttpListener.Portable/Net/ChunkStream.cs
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
class ChunkStream
|
||||||
|
{
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
PartialSize,
|
||||||
|
Body,
|
||||||
|
BodyFinished,
|
||||||
|
Trailer
|
||||||
|
}
|
||||||
|
|
||||||
|
class Chunk
|
||||||
|
{
|
||||||
|
public byte[] Bytes;
|
||||||
|
public int Offset;
|
||||||
|
|
||||||
|
public Chunk(byte[] chunk)
|
||||||
|
{
|
||||||
|
this.Bytes = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Read(byte[] buffer, int offset, int size)
|
||||||
|
{
|
||||||
|
int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size;
|
||||||
|
Buffer.BlockCopy(Bytes, Offset, buffer, offset, nread);
|
||||||
|
Offset += nread;
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WebHeaderCollection headers;
|
||||||
|
int chunkSize;
|
||||||
|
int chunkRead;
|
||||||
|
int totalWritten;
|
||||||
|
State state;
|
||||||
|
//byte [] waitBuffer;
|
||||||
|
StringBuilder saved;
|
||||||
|
bool sawCR;
|
||||||
|
bool gotit;
|
||||||
|
int trailerState;
|
||||||
|
List<Chunk> chunks;
|
||||||
|
|
||||||
|
public ChunkStream(WebHeaderCollection headers)
|
||||||
|
{
|
||||||
|
this.headers = headers;
|
||||||
|
saved = new StringBuilder();
|
||||||
|
chunks = new List<Chunk>();
|
||||||
|
chunkSize = -1;
|
||||||
|
totalWritten = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetBuffer()
|
||||||
|
{
|
||||||
|
chunkSize = -1;
|
||||||
|
chunkRead = 0;
|
||||||
|
totalWritten = 0;
|
||||||
|
chunks.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteAndReadBack(byte[] buffer, int offset, int size, ref int read)
|
||||||
|
{
|
||||||
|
if (offset + read > 0)
|
||||||
|
Write(buffer, offset, offset + read);
|
||||||
|
read = Read(buffer, offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Read(byte[] buffer, int offset, int size)
|
||||||
|
{
|
||||||
|
return ReadFromChunks(buffer, offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadFromChunks(byte[] buffer, int offset, int size)
|
||||||
|
{
|
||||||
|
int count = chunks.Count;
|
||||||
|
int nread = 0;
|
||||||
|
|
||||||
|
var chunksForRemoving = new List<Chunk>(count);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
Chunk chunk = (Chunk)chunks[i];
|
||||||
|
|
||||||
|
if (chunk.Offset == chunk.Bytes.Length)
|
||||||
|
{
|
||||||
|
chunksForRemoving.Add(chunk);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nread += chunk.Read(buffer, offset + nread, size - nread);
|
||||||
|
if (nread == size)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var chunk in chunksForRemoving)
|
||||||
|
chunks.Remove(chunk);
|
||||||
|
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(byte[] buffer, int offset, int size)
|
||||||
|
{
|
||||||
|
if (offset < size)
|
||||||
|
InternalWrite(buffer, ref offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternalWrite(byte[] buffer, ref int offset, int size)
|
||||||
|
{
|
||||||
|
if (state == State.None || state == State.PartialSize)
|
||||||
|
{
|
||||||
|
state = GetChunkSize(buffer, ref offset, size);
|
||||||
|
if (state == State.PartialSize)
|
||||||
|
return;
|
||||||
|
|
||||||
|
saved.Length = 0;
|
||||||
|
sawCR = false;
|
||||||
|
gotit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == State.Body && offset < size)
|
||||||
|
{
|
||||||
|
state = ReadBody(buffer, ref offset, size);
|
||||||
|
if (state == State.Body)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == State.BodyFinished && offset < size)
|
||||||
|
{
|
||||||
|
state = ReadCRLF(buffer, ref offset, size);
|
||||||
|
if (state == State.BodyFinished)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sawCR = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == State.Trailer && offset < size)
|
||||||
|
{
|
||||||
|
state = ReadTrailer(buffer, ref offset, size);
|
||||||
|
if (state == State.Trailer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
saved.Length = 0;
|
||||||
|
sawCR = false;
|
||||||
|
gotit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < size)
|
||||||
|
InternalWrite(buffer, ref offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WantMore
|
||||||
|
{
|
||||||
|
get { return (chunkRead != chunkSize || chunkSize != 0 || state != State.None); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DataAvailable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int count = chunks.Count;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
Chunk ch = (Chunk)chunks[i];
|
||||||
|
if (ch == null || ch.Bytes == null)
|
||||||
|
continue;
|
||||||
|
if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length)
|
||||||
|
return (state != State.Body);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TotalDataSize
|
||||||
|
{
|
||||||
|
get { return totalWritten; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ChunkLeft
|
||||||
|
{
|
||||||
|
get { return chunkSize - chunkRead; }
|
||||||
|
}
|
||||||
|
|
||||||
|
State ReadBody(byte[] buffer, ref int offset, int size)
|
||||||
|
{
|
||||||
|
if (chunkSize == 0)
|
||||||
|
return State.BodyFinished;
|
||||||
|
|
||||||
|
int diff = size - offset;
|
||||||
|
if (diff + chunkRead > chunkSize)
|
||||||
|
diff = chunkSize - chunkRead;
|
||||||
|
|
||||||
|
byte[] chunk = new byte[diff];
|
||||||
|
Buffer.BlockCopy(buffer, offset, chunk, 0, diff);
|
||||||
|
chunks.Add(new Chunk(chunk));
|
||||||
|
offset += diff;
|
||||||
|
chunkRead += diff;
|
||||||
|
totalWritten += diff;
|
||||||
|
return (chunkRead == chunkSize) ? State.BodyFinished : State.Body;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
State GetChunkSize(byte[] buffer, ref int offset, int size)
|
||||||
|
{
|
||||||
|
chunkRead = 0;
|
||||||
|
chunkSize = 0;
|
||||||
|
char c = '\0';
|
||||||
|
while (offset < size)
|
||||||
|
{
|
||||||
|
c = (char)buffer[offset++];
|
||||||
|
if (c == '\r')
|
||||||
|
{
|
||||||
|
if (sawCR)
|
||||||
|
ThrowProtocolViolation("2 CR found");
|
||||||
|
|
||||||
|
sawCR = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sawCR && c == '\n')
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (c == ' ')
|
||||||
|
gotit = true;
|
||||||
|
|
||||||
|
if (!gotit)
|
||||||
|
saved.Append(c);
|
||||||
|
|
||||||
|
if (saved.Length > 20)
|
||||||
|
ThrowProtocolViolation("chunk size too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sawCR || c != '\n')
|
||||||
|
{
|
||||||
|
if (offset < size)
|
||||||
|
ThrowProtocolViolation("Missing \\n");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (saved.Length > 0)
|
||||||
|
{
|
||||||
|
chunkSize = Int32.Parse(RemoveChunkExtension(saved.ToString()), NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
ThrowProtocolViolation("Cannot parse chunk size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return State.PartialSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkRead = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
chunkSize = Int32.Parse(RemoveChunkExtension(saved.ToString()), NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
ThrowProtocolViolation("Cannot parse chunk size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkSize == 0)
|
||||||
|
{
|
||||||
|
trailerState = 2;
|
||||||
|
return State.Trailer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return State.Body;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string RemoveChunkExtension(string input)
|
||||||
|
{
|
||||||
|
int idx = input.IndexOf(';');
|
||||||
|
if (idx == -1)
|
||||||
|
return input;
|
||||||
|
return input.Substring(0, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
State ReadCRLF(byte[] buffer, ref int offset, int size)
|
||||||
|
{
|
||||||
|
if (!sawCR)
|
||||||
|
{
|
||||||
|
if ((char)buffer[offset++] != '\r')
|
||||||
|
ThrowProtocolViolation("Expecting \\r");
|
||||||
|
|
||||||
|
sawCR = true;
|
||||||
|
if (offset == size)
|
||||||
|
return State.BodyFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sawCR && (char)buffer[offset++] != '\n')
|
||||||
|
ThrowProtocolViolation("Expecting \\n");
|
||||||
|
|
||||||
|
return State.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
State ReadTrailer(byte[] buffer, ref int offset, int size)
|
||||||
|
{
|
||||||
|
char c = '\0';
|
||||||
|
|
||||||
|
// short path
|
||||||
|
if (trailerState == 2 && (char)buffer[offset] == '\r' && saved.Length == 0)
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
if (offset < size && (char)buffer[offset] == '\n')
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
return State.None;
|
||||||
|
}
|
||||||
|
offset--;
|
||||||
|
}
|
||||||
|
|
||||||
|
int st = trailerState;
|
||||||
|
string stString = "\r\n\r";
|
||||||
|
while (offset < size && st < 4)
|
||||||
|
{
|
||||||
|
c = (char)buffer[offset++];
|
||||||
|
if ((st == 0 || st == 2) && c == '\r')
|
||||||
|
{
|
||||||
|
st++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((st == 1 || st == 3) && c == '\n')
|
||||||
|
{
|
||||||
|
st++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st > 0)
|
||||||
|
{
|
||||||
|
saved.Append(stString.Substring(0, saved.Length == 0 ? st - 2 : st));
|
||||||
|
st = 0;
|
||||||
|
if (saved.Length > 4196)
|
||||||
|
ThrowProtocolViolation("Error reading trailer (too long).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st < 4)
|
||||||
|
{
|
||||||
|
trailerState = st;
|
||||||
|
if (offset < size)
|
||||||
|
ThrowProtocolViolation("Error reading trailer.");
|
||||||
|
|
||||||
|
return State.Trailer;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringReader reader = new StringReader(saved.ToString());
|
||||||
|
string line;
|
||||||
|
while ((line = reader.ReadLine()) != null && line != "")
|
||||||
|
headers.Add(line);
|
||||||
|
|
||||||
|
return State.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ThrowProtocolViolation(string message)
|
||||||
|
{
|
||||||
|
WebException we = new WebException(message, null, WebExceptionStatus.UnknownError, null);
|
||||||
|
//WebException we = new WebException(message, null, WebExceptionStatus.ServerProtocolViolation, null);
|
||||||
|
throw we;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
160
SocketHttpListener.Portable/Net/ChunkedInputStream.cs
Normal file
160
SocketHttpListener.Portable/Net/ChunkedInputStream.cs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
class ChunkedInputStream : RequestStream
|
||||||
|
{
|
||||||
|
bool disposed;
|
||||||
|
ChunkStream decoder;
|
||||||
|
HttpListenerContext context;
|
||||||
|
bool no_more_data;
|
||||||
|
|
||||||
|
//class ReadBufferState
|
||||||
|
//{
|
||||||
|
// public byte[] Buffer;
|
||||||
|
// public int Offset;
|
||||||
|
// public int Count;
|
||||||
|
// public int InitialCount;
|
||||||
|
// public HttpStreamAsyncResult Ares;
|
||||||
|
// public ReadBufferState(byte[] buffer, int offset, int count,
|
||||||
|
// HttpStreamAsyncResult ares)
|
||||||
|
// {
|
||||||
|
// Buffer = buffer;
|
||||||
|
// Offset = offset;
|
||||||
|
// Count = count;
|
||||||
|
// InitialCount = count;
|
||||||
|
// Ares = ares;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
public ChunkedInputStream(HttpListenerContext context, Stream stream,
|
||||||
|
byte[] buffer, int offset, int length)
|
||||||
|
: base(stream, buffer, offset, length)
|
||||||
|
{
|
||||||
|
this.context = context;
|
||||||
|
WebHeaderCollection coll = (WebHeaderCollection)context.Request.Headers;
|
||||||
|
decoder = new ChunkStream(coll);
|
||||||
|
}
|
||||||
|
|
||||||
|
//public ChunkStream Decoder
|
||||||
|
//{
|
||||||
|
// get { return decoder; }
|
||||||
|
// set { decoder = value; }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public override int Read([In, Out] byte[] buffer, int offset, int count)
|
||||||
|
//{
|
||||||
|
// IAsyncResult ares = BeginRead(buffer, offset, count, null, null);
|
||||||
|
// return EndRead(ares);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public override IAsyncResult BeginRead(byte[] buffer, int offset, int count,
|
||||||
|
// AsyncCallback cback, object state)
|
||||||
|
//{
|
||||||
|
// if (disposed)
|
||||||
|
// throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
// if (buffer == null)
|
||||||
|
// throw new ArgumentNullException("buffer");
|
||||||
|
|
||||||
|
// int len = buffer.Length;
|
||||||
|
// if (offset < 0 || offset > len)
|
||||||
|
// throw new ArgumentOutOfRangeException("offset exceeds the size of buffer");
|
||||||
|
|
||||||
|
// if (count < 0 || offset > len - count)
|
||||||
|
// throw new ArgumentOutOfRangeException("offset+size exceeds the size of buffer");
|
||||||
|
|
||||||
|
// HttpStreamAsyncResult ares = new HttpStreamAsyncResult();
|
||||||
|
// ares.Callback = cback;
|
||||||
|
// ares.State = state;
|
||||||
|
// if (no_more_data)
|
||||||
|
// {
|
||||||
|
// ares.Complete();
|
||||||
|
// return ares;
|
||||||
|
// }
|
||||||
|
// int nread = decoder.Read(buffer, offset, count);
|
||||||
|
// offset += nread;
|
||||||
|
// count -= nread;
|
||||||
|
// if (count == 0)
|
||||||
|
// {
|
||||||
|
// // got all we wanted, no need to bother the decoder yet
|
||||||
|
// ares.Count = nread;
|
||||||
|
// ares.Complete();
|
||||||
|
// return ares;
|
||||||
|
// }
|
||||||
|
// if (!decoder.WantMore)
|
||||||
|
// {
|
||||||
|
// no_more_data = nread == 0;
|
||||||
|
// ares.Count = nread;
|
||||||
|
// ares.Complete();
|
||||||
|
// return ares;
|
||||||
|
// }
|
||||||
|
// ares.Buffer = new byte[8192];
|
||||||
|
// ares.Offset = 0;
|
||||||
|
// ares.Count = 8192;
|
||||||
|
// ReadBufferState rb = new ReadBufferState(buffer, offset, count, ares);
|
||||||
|
// rb.InitialCount += nread;
|
||||||
|
// base.BeginRead(ares.Buffer, ares.Offset, ares.Count, OnRead, rb);
|
||||||
|
// return ares;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//void OnRead(IAsyncResult base_ares)
|
||||||
|
//{
|
||||||
|
// ReadBufferState rb = (ReadBufferState)base_ares.AsyncState;
|
||||||
|
// HttpStreamAsyncResult ares = rb.Ares;
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// int nread = base.EndRead(base_ares);
|
||||||
|
// decoder.Write(ares.Buffer, ares.Offset, nread);
|
||||||
|
// nread = decoder.Read(rb.Buffer, rb.Offset, rb.Count);
|
||||||
|
// rb.Offset += nread;
|
||||||
|
// rb.Count -= nread;
|
||||||
|
// if (rb.Count == 0 || !decoder.WantMore || nread == 0)
|
||||||
|
// {
|
||||||
|
// no_more_data = !decoder.WantMore && nread == 0;
|
||||||
|
// ares.Count = rb.InitialCount - rb.Count;
|
||||||
|
// ares.Complete();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// ares.Offset = 0;
|
||||||
|
// ares.Count = Math.Min(8192, decoder.ChunkLeft + 6);
|
||||||
|
// base.BeginRead(ares.Buffer, ares.Offset, ares.Count, OnRead, rb);
|
||||||
|
// }
|
||||||
|
// catch (Exception e)
|
||||||
|
// {
|
||||||
|
// context.Connection.SendError(e.Message, 400);
|
||||||
|
// ares.Complete(e);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public override int EndRead(IAsyncResult ares)
|
||||||
|
//{
|
||||||
|
// if (disposed)
|
||||||
|
// throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
// HttpStreamAsyncResult my_ares = ares as HttpStreamAsyncResult;
|
||||||
|
// if (ares == null)
|
||||||
|
// throw new ArgumentException("Invalid IAsyncResult", "ares");
|
||||||
|
|
||||||
|
// if (!ares.IsCompleted)
|
||||||
|
// ares.AsyncWaitHandle.WaitOne();
|
||||||
|
|
||||||
|
// if (my_ares.Error != null)
|
||||||
|
// throw new HttpListenerException(400, "I/O operation aborted: " + my_ares.Error.Message);
|
||||||
|
|
||||||
|
// return my_ares.Count;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//protected override void Dispose(bool disposing)
|
||||||
|
//{
|
||||||
|
// if (!disposed)
|
||||||
|
// {
|
||||||
|
// disposed = true;
|
||||||
|
// base.Dispose(disposing);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
144
SocketHttpListener.Portable/Net/CookieHelper.cs
Normal file
144
SocketHttpListener.Portable/Net/CookieHelper.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
public static class CookieHelper
|
||||||
|
{
|
||||||
|
internal static CookieCollection Parse(string value, bool response)
|
||||||
|
{
|
||||||
|
return response
|
||||||
|
? parseResponse(value)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] splitCookieHeaderValue(string value)
|
||||||
|
{
|
||||||
|
return new List<string>(value.SplitHeaderValue(',', ';')).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CookieCollection parseResponse(string value)
|
||||||
|
{
|
||||||
|
var cookies = new CookieCollection();
|
||||||
|
|
||||||
|
Cookie cookie = null;
|
||||||
|
var pairs = splitCookieHeaderValue(value);
|
||||||
|
for (int i = 0; i < pairs.Length; i++)
|
||||||
|
{
|
||||||
|
var pair = pairs[i].Trim();
|
||||||
|
if (pair.Length == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (cookie != null)
|
||||||
|
cookie.Version = Int32.Parse(pair.GetValueInternal("=").Trim('"'));
|
||||||
|
}
|
||||||
|
else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var buffer = new StringBuilder(pair.GetValueInternal("="), 32);
|
||||||
|
if (i < pairs.Length - 1)
|
||||||
|
buffer.AppendFormat(", {0}", pairs[++i].Trim());
|
||||||
|
|
||||||
|
DateTime expires;
|
||||||
|
if (!DateTime.TryParseExact(
|
||||||
|
buffer.ToString(),
|
||||||
|
new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" },
|
||||||
|
new CultureInfo("en-US"),
|
||||||
|
DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal,
|
||||||
|
out expires))
|
||||||
|
expires = DateTime.Now;
|
||||||
|
|
||||||
|
if (cookie != null && cookie.Expires == DateTime.MinValue)
|
||||||
|
cookie.Expires = expires.ToLocalTime();
|
||||||
|
}
|
||||||
|
else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var max = Int32.Parse(pair.GetValueInternal("=").Trim('"'));
|
||||||
|
var expires = DateTime.Now.AddSeconds((double)max);
|
||||||
|
if (cookie != null)
|
||||||
|
cookie.Expires = expires;
|
||||||
|
}
|
||||||
|
else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (cookie != null)
|
||||||
|
cookie.Path = pair.GetValueInternal("=");
|
||||||
|
}
|
||||||
|
else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (cookie != null)
|
||||||
|
cookie.Domain = pair.GetValueInternal("=");
|
||||||
|
}
|
||||||
|
else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var port = pair.Equals("port", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? "\"\""
|
||||||
|
: pair.GetValueInternal("=");
|
||||||
|
|
||||||
|
if (cookie != null)
|
||||||
|
cookie.Port = port;
|
||||||
|
}
|
||||||
|
else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (cookie != null)
|
||||||
|
cookie.Comment = pair.GetValueInternal("=").UrlDecode();
|
||||||
|
}
|
||||||
|
else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (cookie != null)
|
||||||
|
cookie.CommentUri = pair.GetValueInternal("=").Trim('"').ToUri();
|
||||||
|
}
|
||||||
|
else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (cookie != null)
|
||||||
|
cookie.Discard = true;
|
||||||
|
}
|
||||||
|
else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (cookie != null)
|
||||||
|
cookie.Secure = true;
|
||||||
|
}
|
||||||
|
else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (cookie != null)
|
||||||
|
cookie.HttpOnly = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (cookie != null)
|
||||||
|
cookies.Add(cookie);
|
||||||
|
|
||||||
|
string name;
|
||||||
|
string 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(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie = new Cookie(name, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie != null)
|
||||||
|
cookies.Add(cookie);
|
||||||
|
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
368
SocketHttpListener.Portable/Net/EndPointListener.cs
Normal file
368
SocketHttpListener.Portable/Net/EndPointListener.cs
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Text;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
sealed class EndPointListener
|
||||||
|
{
|
||||||
|
HttpListener listener;
|
||||||
|
IpEndPointInfo endpoint;
|
||||||
|
ISocket sock;
|
||||||
|
Dictionary<ListenerPrefix,HttpListener> prefixes; // Dictionary <ListenerPrefix, HttpListener>
|
||||||
|
List<ListenerPrefix> unhandled; // List<ListenerPrefix> unhandled; host = '*'
|
||||||
|
List<ListenerPrefix> all; // List<ListenerPrefix> all; host = '+'
|
||||||
|
ICertificate cert;
|
||||||
|
bool secure;
|
||||||
|
Dictionary<HttpConnection, HttpConnection> unregistered;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private bool _closed;
|
||||||
|
private readonly bool _enableDualMode;
|
||||||
|
private readonly ICryptoProvider _cryptoProvider;
|
||||||
|
private readonly IStreamFactory _streamFactory;
|
||||||
|
private readonly ISocketFactory _socketFactory;
|
||||||
|
private readonly ITextEncoding _textEncoding;
|
||||||
|
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||||
|
|
||||||
|
public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
|
||||||
|
{
|
||||||
|
this.listener = listener;
|
||||||
|
_logger = logger;
|
||||||
|
_cryptoProvider = cryptoProvider;
|
||||||
|
_streamFactory = streamFactory;
|
||||||
|
_socketFactory = socketFactory;
|
||||||
|
_memoryStreamFactory = memoryStreamFactory;
|
||||||
|
_textEncoding = textEncoding;
|
||||||
|
|
||||||
|
this.secure = secure;
|
||||||
|
this.cert = cert;
|
||||||
|
|
||||||
|
_enableDualMode = addr.Equals(IpAddressInfo.IPv6Any);
|
||||||
|
endpoint = new IpEndPointInfo(addr, port);
|
||||||
|
|
||||||
|
prefixes = new Dictionary<ListenerPrefix, HttpListener>();
|
||||||
|
unregistered = new Dictionary<HttpConnection, HttpConnection>();
|
||||||
|
|
||||||
|
CreateSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal HttpListener Listener
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateSocket()
|
||||||
|
{
|
||||||
|
sock = _socketFactory.CreateSocket(endpoint.IpAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, _enableDualMode);
|
||||||
|
|
||||||
|
sock.Bind(endpoint);
|
||||||
|
|
||||||
|
// This is the number TcpListener uses.
|
||||||
|
sock.Listen(2147483647);
|
||||||
|
|
||||||
|
sock.StartAccept(ProcessAccept, () => _closed);
|
||||||
|
_closed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ProcessAccept(ISocket accepted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var listener = this;
|
||||||
|
|
||||||
|
if (listener.secure && listener.cert == null)
|
||||||
|
{
|
||||||
|
accepted.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId);
|
||||||
|
lock (listener.unregistered)
|
||||||
|
{
|
||||||
|
listener.unregistered[conn] = conn;
|
||||||
|
}
|
||||||
|
conn.BeginReadRequest();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error in ProcessAccept", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RemoveConnection(HttpConnection conn)
|
||||||
|
{
|
||||||
|
lock (unregistered)
|
||||||
|
{
|
||||||
|
unregistered.Remove(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BindContext(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
HttpListenerRequest req = context.Request;
|
||||||
|
ListenerPrefix prefix;
|
||||||
|
HttpListener listener = SearchListener(req.Url, out prefix);
|
||||||
|
if (listener == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
context.Listener = listener;
|
||||||
|
context.Connection.Prefix = prefix;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnbindContext(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
if (context == null || context.Request == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.Listener.UnregisterContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpListener SearchListener(Uri uri, out ListenerPrefix prefix)
|
||||||
|
{
|
||||||
|
prefix = null;
|
||||||
|
if (uri == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
string host = uri.Host;
|
||||||
|
int port = uri.Port;
|
||||||
|
string path = WebUtility.UrlDecode(uri.AbsolutePath);
|
||||||
|
string path_slash = path[path.Length - 1] == '/' ? path : path + "/";
|
||||||
|
|
||||||
|
HttpListener best_match = null;
|
||||||
|
int best_length = -1;
|
||||||
|
|
||||||
|
if (host != null && host != "")
|
||||||
|
{
|
||||||
|
var p_ro = prefixes;
|
||||||
|
foreach (ListenerPrefix p in p_ro.Keys)
|
||||||
|
{
|
||||||
|
string ppath = p.Path;
|
||||||
|
if (ppath.Length < best_length)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (p.Host != host || p.Port != port)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (path.StartsWith(ppath) || path_slash.StartsWith(ppath))
|
||||||
|
{
|
||||||
|
best_length = ppath.Length;
|
||||||
|
best_match = (HttpListener)p_ro[p];
|
||||||
|
prefix = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best_length != -1)
|
||||||
|
return best_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ListenerPrefix> list = unhandled;
|
||||||
|
best_match = MatchFromList(host, path, list, out prefix);
|
||||||
|
if (path != path_slash && best_match == null)
|
||||||
|
best_match = MatchFromList(host, path_slash, list, out prefix);
|
||||||
|
if (best_match != null)
|
||||||
|
return best_match;
|
||||||
|
|
||||||
|
list = all;
|
||||||
|
best_match = MatchFromList(host, path, list, out prefix);
|
||||||
|
if (path != path_slash && best_match == null)
|
||||||
|
best_match = MatchFromList(host, path_slash, list, out prefix);
|
||||||
|
if (best_match != null)
|
||||||
|
return best_match;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpListener MatchFromList(string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix)
|
||||||
|
{
|
||||||
|
prefix = null;
|
||||||
|
if (list == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
HttpListener best_match = null;
|
||||||
|
int best_length = -1;
|
||||||
|
|
||||||
|
foreach (ListenerPrefix p in list)
|
||||||
|
{
|
||||||
|
string ppath = p.Path;
|
||||||
|
if (ppath.Length < best_length)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (path.StartsWith(ppath))
|
||||||
|
{
|
||||||
|
best_length = ppath.Length;
|
||||||
|
best_match = p.Listener;
|
||||||
|
prefix = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddSpecial(List<ListenerPrefix> coll, ListenerPrefix prefix)
|
||||||
|
{
|
||||||
|
if (coll == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (ListenerPrefix p in coll)
|
||||||
|
{
|
||||||
|
if (p.Path == prefix.Path) //TODO: code
|
||||||
|
throw new HttpListenerException(400, "Prefix already in use.");
|
||||||
|
}
|
||||||
|
coll.Add(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoveSpecial(List<ListenerPrefix> coll, ListenerPrefix prefix)
|
||||||
|
{
|
||||||
|
if (coll == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int c = coll.Count;
|
||||||
|
for (int i = 0; i < c; i++)
|
||||||
|
{
|
||||||
|
ListenerPrefix p = (ListenerPrefix)coll[i];
|
||||||
|
if (p.Path == prefix.Path)
|
||||||
|
{
|
||||||
|
coll.RemoveAt(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckIfRemove()
|
||||||
|
{
|
||||||
|
if (prefixes.Count > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
List<ListenerPrefix> list = unhandled;
|
||||||
|
if (list != null && list.Count > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
list = all;
|
||||||
|
if (list != null && list.Count > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EndPointManager.RemoveEndPoint(this, endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
_closed = true;
|
||||||
|
sock.Close();
|
||||||
|
lock (unregistered)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Clone the list because RemoveConnection can be called from Close
|
||||||
|
//
|
||||||
|
var connections = new List<HttpConnection>(unregistered.Keys);
|
||||||
|
|
||||||
|
foreach (HttpConnection c in connections)
|
||||||
|
c.Close(true);
|
||||||
|
unregistered.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPrefix(ListenerPrefix prefix, HttpListener listener)
|
||||||
|
{
|
||||||
|
List<ListenerPrefix> current;
|
||||||
|
List<ListenerPrefix> future;
|
||||||
|
if (prefix.Host == "*")
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
current = unhandled;
|
||||||
|
future = (current != null) ? current.ToList() : new List<ListenerPrefix>();
|
||||||
|
prefix.Listener = listener;
|
||||||
|
AddSpecial(future, prefix);
|
||||||
|
} while (Interlocked.CompareExchange(ref unhandled, future, current) != current);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix.Host == "+")
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
current = all;
|
||||||
|
future = (current != null) ? current.ToList() : new List<ListenerPrefix>();
|
||||||
|
prefix.Listener = listener;
|
||||||
|
AddSpecial(future, prefix);
|
||||||
|
} while (Interlocked.CompareExchange(ref all, future, current) != current);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<ListenerPrefix, HttpListener> prefs;
|
||||||
|
Dictionary<ListenerPrefix, HttpListener> p2;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
prefs = prefixes;
|
||||||
|
if (prefs.ContainsKey(prefix))
|
||||||
|
{
|
||||||
|
HttpListener other = (HttpListener)prefs[prefix];
|
||||||
|
if (other != listener) // TODO: code.
|
||||||
|
throw new HttpListenerException(400, "There's another listener for " + prefix);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
|
||||||
|
p2[prefix] = listener;
|
||||||
|
} while (Interlocked.CompareExchange(ref prefixes, p2, prefs) != prefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovePrefix(ListenerPrefix prefix, HttpListener listener)
|
||||||
|
{
|
||||||
|
List<ListenerPrefix> current;
|
||||||
|
List<ListenerPrefix> future;
|
||||||
|
if (prefix.Host == "*")
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
current = unhandled;
|
||||||
|
future = (current != null) ? current.ToList() : new List<ListenerPrefix>();
|
||||||
|
if (!RemoveSpecial(future, prefix))
|
||||||
|
break; // Prefix not found
|
||||||
|
} while (Interlocked.CompareExchange(ref unhandled, future, current) != current);
|
||||||
|
CheckIfRemove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix.Host == "+")
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
current = all;
|
||||||
|
future = (current != null) ? current.ToList() : new List<ListenerPrefix>();
|
||||||
|
if (!RemoveSpecial(future, prefix))
|
||||||
|
break; // Prefix not found
|
||||||
|
} while (Interlocked.CompareExchange(ref all, future, current) != current);
|
||||||
|
CheckIfRemove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<ListenerPrefix, HttpListener> prefs;
|
||||||
|
Dictionary<ListenerPrefix, HttpListener> p2;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
prefs = prefixes;
|
||||||
|
if (!prefs.ContainsKey(prefix))
|
||||||
|
break;
|
||||||
|
|
||||||
|
p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
|
||||||
|
p2.Remove(prefix);
|
||||||
|
} while (Interlocked.CompareExchange(ref prefixes, p2, prefs) != prefs);
|
||||||
|
CheckIfRemove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
165
SocketHttpListener.Portable/Net/EndPointManager.cs
Normal file
165
SocketHttpListener.Portable/Net/EndPointManager.cs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
sealed class EndPointManager
|
||||||
|
{
|
||||||
|
// Dictionary<IPAddress, Dictionary<int, EndPointListener>>
|
||||||
|
static Dictionary<string, Dictionary<int, EndPointListener>> ip_to_endpoints = new Dictionary<string, Dictionary<int, EndPointListener>>();
|
||||||
|
|
||||||
|
private EndPointManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddListener(ILogger logger, HttpListener listener)
|
||||||
|
{
|
||||||
|
List<string> added = new List<string>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (ip_to_endpoints)
|
||||||
|
{
|
||||||
|
foreach (string prefix in listener.Prefixes)
|
||||||
|
{
|
||||||
|
AddPrefixInternal(logger, prefix, listener);
|
||||||
|
added.Add(prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
foreach (string prefix in added)
|
||||||
|
{
|
||||||
|
RemovePrefix(logger, prefix, listener);
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddPrefix(ILogger logger, string prefix, HttpListener listener)
|
||||||
|
{
|
||||||
|
lock (ip_to_endpoints)
|
||||||
|
{
|
||||||
|
AddPrefixInternal(logger, prefix, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddPrefixInternal(ILogger logger, string p, HttpListener listener)
|
||||||
|
{
|
||||||
|
ListenerPrefix lp = new ListenerPrefix(p);
|
||||||
|
if (lp.Path.IndexOf('%') != -1)
|
||||||
|
throw new HttpListenerException(400, "Invalid path.");
|
||||||
|
|
||||||
|
if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) // TODO: Code?
|
||||||
|
throw new HttpListenerException(400, "Invalid path.");
|
||||||
|
|
||||||
|
// listens on all the interfaces if host name cannot be parsed by IPAddress.
|
||||||
|
EndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure).Result;
|
||||||
|
epl.AddPrefix(lp, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IpAddressInfo GetIpAnyAddress(HttpListener listener)
|
||||||
|
{
|
||||||
|
return listener.EnableDualMode ? IpAddressInfo.IPv6Any : IpAddressInfo.Any;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task<EndPointListener> GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure)
|
||||||
|
{
|
||||||
|
var networkManager = listener.NetworkManager;
|
||||||
|
|
||||||
|
IpAddressInfo addr;
|
||||||
|
if (host == "*" || host == "+")
|
||||||
|
addr = GetIpAnyAddress(listener);
|
||||||
|
else if (networkManager.TryParseIpAddress(host, out addr) == false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
addr = (await networkManager.GetHostAddressesAsync(host).ConfigureAwait(false)).FirstOrDefault() ??
|
||||||
|
GetIpAnyAddress(listener);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
addr = GetIpAnyAddress(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<int, EndPointListener> p = null; // Dictionary<int, EndPointListener>
|
||||||
|
if (!ip_to_endpoints.TryGetValue(addr.Address, out p))
|
||||||
|
{
|
||||||
|
p = new Dictionary<int, EndPointListener>();
|
||||||
|
ip_to_endpoints[addr.Address] = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndPointListener epl = null;
|
||||||
|
if (p.ContainsKey(port))
|
||||||
|
{
|
||||||
|
epl = (EndPointListener)p[port];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding);
|
||||||
|
p[port] = epl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return epl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemoveEndPoint(EndPointListener epl, IpEndPointInfo ep)
|
||||||
|
{
|
||||||
|
lock (ip_to_endpoints)
|
||||||
|
{
|
||||||
|
// Dictionary<int, EndPointListener> p
|
||||||
|
Dictionary<int, EndPointListener> p;
|
||||||
|
if (ip_to_endpoints.TryGetValue(ep.IpAddress.Address, out p))
|
||||||
|
{
|
||||||
|
p.Remove(ep.Port);
|
||||||
|
if (p.Count == 0)
|
||||||
|
{
|
||||||
|
ip_to_endpoints.Remove(ep.IpAddress.Address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
epl.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemoveListener(ILogger logger, HttpListener listener)
|
||||||
|
{
|
||||||
|
lock (ip_to_endpoints)
|
||||||
|
{
|
||||||
|
foreach (string prefix in listener.Prefixes)
|
||||||
|
{
|
||||||
|
RemovePrefixInternal(logger, prefix, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemovePrefix(ILogger logger, string prefix, HttpListener listener)
|
||||||
|
{
|
||||||
|
lock (ip_to_endpoints)
|
||||||
|
{
|
||||||
|
RemovePrefixInternal(logger, prefix, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RemovePrefixInternal(ILogger logger, string prefix, HttpListener listener)
|
||||||
|
{
|
||||||
|
ListenerPrefix lp = new ListenerPrefix(prefix);
|
||||||
|
if (lp.Path.IndexOf('%') != -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure).Result;
|
||||||
|
epl.RemovePrefix(lp, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
550
SocketHttpListener.Portable/Net/HttpConnection.cs
Normal file
550
SocketHttpListener.Portable/Net/HttpConnection.cs
Normal file
@ -0,0 +1,550 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Text;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
sealed class HttpConnection
|
||||||
|
{
|
||||||
|
const int BufferSize = 8192;
|
||||||
|
ISocket sock;
|
||||||
|
Stream stream;
|
||||||
|
EndPointListener epl;
|
||||||
|
MemoryStream ms;
|
||||||
|
byte[] buffer;
|
||||||
|
HttpListenerContext context;
|
||||||
|
StringBuilder current_line;
|
||||||
|
ListenerPrefix prefix;
|
||||||
|
RequestStream i_stream;
|
||||||
|
ResponseStream o_stream;
|
||||||
|
bool chunked;
|
||||||
|
int reuses;
|
||||||
|
bool context_bound;
|
||||||
|
bool secure;
|
||||||
|
int s_timeout = 300000; // 90k ms for first request, 15k ms from then on
|
||||||
|
IpEndPointInfo local_ep;
|
||||||
|
HttpListener last_listener;
|
||||||
|
int[] client_cert_errors;
|
||||||
|
ICertificate cert;
|
||||||
|
Stream ssl_stream;
|
||||||
|
|
||||||
|
private ILogger _logger;
|
||||||
|
private readonly ICryptoProvider _cryptoProvider;
|
||||||
|
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||||
|
private readonly ITextEncoding _textEncoding;
|
||||||
|
private readonly IStreamFactory _streamFactory;
|
||||||
|
|
||||||
|
private HttpConnection(ILogger logger, ISocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
this.sock = sock;
|
||||||
|
this.epl = epl;
|
||||||
|
this.secure = secure;
|
||||||
|
this.cert = cert;
|
||||||
|
_cryptoProvider = cryptoProvider;
|
||||||
|
_memoryStreamFactory = memoryStreamFactory;
|
||||||
|
_textEncoding = textEncoding;
|
||||||
|
_streamFactory = streamFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InitStream()
|
||||||
|
{
|
||||||
|
if (secure == false)
|
||||||
|
{
|
||||||
|
stream = _streamFactory.CreateNetworkStream(sock, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//ssl_stream = epl.Listener.CreateSslStream(new NetworkStream(sock, false), false, (t, c, ch, e) =>
|
||||||
|
//{
|
||||||
|
// if (c == null)
|
||||||
|
// return true;
|
||||||
|
// var c2 = c as X509Certificate2;
|
||||||
|
// if (c2 == null)
|
||||||
|
// c2 = new X509Certificate2(c.GetRawCertData());
|
||||||
|
// client_cert = c2;
|
||||||
|
// client_cert_errors = new int[] { (int)e };
|
||||||
|
// return true;
|
||||||
|
//});
|
||||||
|
//stream = ssl_stream.AuthenticatedStream;
|
||||||
|
|
||||||
|
ssl_stream = _streamFactory.CreateSslStream(_streamFactory.CreateNetworkStream(sock, false), false);
|
||||||
|
await _streamFactory.AuthenticateSslStreamAsServer(ssl_stream, cert).ConfigureAwait(false);
|
||||||
|
stream = ssl_stream;
|
||||||
|
}
|
||||||
|
Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<HttpConnection> Create(ILogger logger, ISocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
|
||||||
|
{
|
||||||
|
var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding);
|
||||||
|
|
||||||
|
await connection.InitStream().ConfigureAwait(false);
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream Stream
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int[] ClientCertificateErrors
|
||||||
|
{
|
||||||
|
get { return client_cert_errors; }
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init()
|
||||||
|
{
|
||||||
|
if (ssl_stream != null)
|
||||||
|
{
|
||||||
|
//ssl_stream.AuthenticateAsServer(client_cert, true, (SslProtocols)ServicePointManager.SecurityProtocol, false);
|
||||||
|
//_streamFactory.AuthenticateSslStreamAsServer(ssl_stream, cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
context_bound = false;
|
||||||
|
i_stream = null;
|
||||||
|
o_stream = null;
|
||||||
|
prefix = null;
|
||||||
|
chunked = false;
|
||||||
|
ms = _memoryStreamFactory.CreateNew();
|
||||||
|
position = 0;
|
||||||
|
input_state = InputState.RequestLine;
|
||||||
|
line_state = LineState.None;
|
||||||
|
context = new HttpListenerContext(this, _logger, _cryptoProvider, _memoryStreamFactory, _textEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsClosed
|
||||||
|
{
|
||||||
|
get { return (sock == null); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Reuses
|
||||||
|
{
|
||||||
|
get { return reuses; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IpEndPointInfo LocalEndPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (local_ep != null)
|
||||||
|
return local_ep;
|
||||||
|
|
||||||
|
local_ep = (IpEndPointInfo)sock.LocalEndPoint;
|
||||||
|
return local_ep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IpEndPointInfo RemoteEndPoint
|
||||||
|
{
|
||||||
|
get { return (IpEndPointInfo)sock.RemoteEndPoint; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSecure
|
||||||
|
{
|
||||||
|
get { return secure; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenerPrefix Prefix
|
||||||
|
{
|
||||||
|
get { return prefix; }
|
||||||
|
set { prefix = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task BeginReadRequest()
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
buffer = new byte[BufferSize];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//if (reuses == 1)
|
||||||
|
// s_timeout = 15000;
|
||||||
|
var nRead = await stream.ReadAsync(buffer, 0, BufferSize).ConfigureAwait(false);
|
||||||
|
|
||||||
|
OnReadInternal(nRead);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
OnReadInternalException(ms, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestStream GetRequestStream(bool chunked, long contentlength)
|
||||||
|
{
|
||||||
|
if (i_stream == null)
|
||||||
|
{
|
||||||
|
byte[] buffer;
|
||||||
|
_memoryStreamFactory.TryGetBuffer(ms, out buffer);
|
||||||
|
|
||||||
|
int length = (int)ms.Length;
|
||||||
|
ms = null;
|
||||||
|
if (chunked)
|
||||||
|
{
|
||||||
|
this.chunked = true;
|
||||||
|
//context.Response.SendChunked = true;
|
||||||
|
i_stream = new ChunkedInputStream(context, stream, buffer, position, length - position);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
i_stream = new RequestStream(stream, buffer, position, length - position, contentlength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseStream GetResponseStream()
|
||||||
|
{
|
||||||
|
// TODO: can we get this stream before reading the input?
|
||||||
|
if (o_stream == null)
|
||||||
|
{
|
||||||
|
HttpListener listener = context.Listener;
|
||||||
|
|
||||||
|
if (listener == null)
|
||||||
|
return new ResponseStream(stream, context.Response, true, _memoryStreamFactory, _textEncoding);
|
||||||
|
|
||||||
|
o_stream = new ResponseStream(stream, context.Response, listener.IgnoreWriteExceptions, _memoryStreamFactory, _textEncoding);
|
||||||
|
}
|
||||||
|
return o_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnReadInternal(int nread)
|
||||||
|
{
|
||||||
|
ms.Write(buffer, 0, nread);
|
||||||
|
if (ms.Length > 32768)
|
||||||
|
{
|
||||||
|
SendError("Bad request", 400);
|
||||||
|
Close(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nread == 0)
|
||||||
|
{
|
||||||
|
//if (ms.Length > 0)
|
||||||
|
// SendError (); // Why bother?
|
||||||
|
CloseSocket();
|
||||||
|
Unbind();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ProcessInput(ms))
|
||||||
|
{
|
||||||
|
if (!context.HaveError)
|
||||||
|
context.Request.FinishInitialization();
|
||||||
|
|
||||||
|
if (context.HaveError)
|
||||||
|
{
|
||||||
|
SendError();
|
||||||
|
Close(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!epl.BindContext(context))
|
||||||
|
{
|
||||||
|
SendError("Invalid host", 400);
|
||||||
|
Close(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HttpListener listener = context.Listener;
|
||||||
|
if (last_listener != listener)
|
||||||
|
{
|
||||||
|
RemoveConnection();
|
||||||
|
listener.AddConnection(this);
|
||||||
|
last_listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
context_bound = true;
|
||||||
|
listener.RegisterContext(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginReadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnReadInternalException(MemoryStream ms, Exception ex)
|
||||||
|
{
|
||||||
|
//_logger.ErrorException("Error in HttpConnection.OnReadInternal", ex);
|
||||||
|
|
||||||
|
if (ms != null && ms.Length > 0)
|
||||||
|
SendError();
|
||||||
|
if (sock != null)
|
||||||
|
{
|
||||||
|
CloseSocket();
|
||||||
|
Unbind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveConnection()
|
||||||
|
{
|
||||||
|
if (last_listener == null)
|
||||||
|
epl.RemoveConnection(this);
|
||||||
|
else
|
||||||
|
last_listener.RemoveConnection(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InputState
|
||||||
|
{
|
||||||
|
RequestLine,
|
||||||
|
Headers
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LineState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
CR,
|
||||||
|
LF
|
||||||
|
}
|
||||||
|
|
||||||
|
InputState input_state = InputState.RequestLine;
|
||||||
|
LineState line_state = LineState.None;
|
||||||
|
int position;
|
||||||
|
|
||||||
|
// true -> done processing
|
||||||
|
// false -> need more input
|
||||||
|
bool ProcessInput(MemoryStream ms)
|
||||||
|
{
|
||||||
|
byte[] buffer;
|
||||||
|
_memoryStreamFactory.TryGetBuffer(ms, out buffer);
|
||||||
|
|
||||||
|
int len = (int)ms.Length;
|
||||||
|
int used = 0;
|
||||||
|
string line;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (context.HaveError)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (position >= len)
|
||||||
|
break;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
line = ReadLine(buffer, position, len - position, ref used);
|
||||||
|
position += used;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
context.ErrorMessage = "Bad request";
|
||||||
|
context.ErrorStatus = 400;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (line == "")
|
||||||
|
{
|
||||||
|
if (input_state == InputState.RequestLine)
|
||||||
|
continue;
|
||||||
|
current_line = null;
|
||||||
|
ms = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_state == InputState.RequestLine)
|
||||||
|
{
|
||||||
|
context.Request.SetRequestLine(line);
|
||||||
|
input_state = InputState.Headers;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
context.Request.AddHeader(line);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
context.ErrorMessage = e.Message;
|
||||||
|
context.ErrorStatus = 400;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (used == len)
|
||||||
|
{
|
||||||
|
ms.SetLength(0);
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ReadLine(byte[] buffer, int offset, int len, ref int used)
|
||||||
|
{
|
||||||
|
if (current_line == null)
|
||||||
|
current_line = new StringBuilder(128);
|
||||||
|
int last = offset + len;
|
||||||
|
used = 0;
|
||||||
|
|
||||||
|
for (int i = offset; i < last && line_state != LineState.LF; i++)
|
||||||
|
{
|
||||||
|
used++;
|
||||||
|
byte b = buffer[i];
|
||||||
|
if (b == 13)
|
||||||
|
{
|
||||||
|
line_state = LineState.CR;
|
||||||
|
}
|
||||||
|
else if (b == 10)
|
||||||
|
{
|
||||||
|
line_state = LineState.LF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current_line.Append((char)b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string result = null;
|
||||||
|
if (line_state == LineState.LF)
|
||||||
|
{
|
||||||
|
line_state = LineState.None;
|
||||||
|
result = current_line.ToString();
|
||||||
|
current_line.Length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendError(string msg, int status)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpListenerResponse response = context.Response;
|
||||||
|
response.StatusCode = status;
|
||||||
|
response.ContentType = "text/html";
|
||||||
|
string description = HttpListenerResponse.GetStatusDescription(status);
|
||||||
|
string str;
|
||||||
|
if (msg != null)
|
||||||
|
str = String.Format("<h1>{0} ({1})</h1>", description, msg);
|
||||||
|
else
|
||||||
|
str = String.Format("<h1>{0}</h1>", description);
|
||||||
|
|
||||||
|
byte[] error = context.Response.ContentEncoding.GetBytes(str);
|
||||||
|
response.Close(error, false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// response was already closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendError()
|
||||||
|
{
|
||||||
|
SendError(context.ErrorMessage, context.ErrorStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unbind()
|
||||||
|
{
|
||||||
|
if (context_bound)
|
||||||
|
{
|
||||||
|
epl.UnbindContext(context);
|
||||||
|
context_bound = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
Close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseSocket()
|
||||||
|
{
|
||||||
|
if (sock == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sock.Close();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
sock = null;
|
||||||
|
}
|
||||||
|
RemoveConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Close(bool force_close)
|
||||||
|
{
|
||||||
|
if (sock != null)
|
||||||
|
{
|
||||||
|
if (!context.Request.IsWebSocketRequest || force_close)
|
||||||
|
{
|
||||||
|
Stream st = GetResponseStream();
|
||||||
|
if (st != null)
|
||||||
|
st.Dispose();
|
||||||
|
|
||||||
|
o_stream = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock != null)
|
||||||
|
{
|
||||||
|
force_close |= !context.Request.KeepAlive;
|
||||||
|
if (!force_close)
|
||||||
|
force_close = (context.Response.Headers["connection"] == "close");
|
||||||
|
/*
|
||||||
|
if (!force_close) {
|
||||||
|
// bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
|
||||||
|
// status_code == 413 || status_code == 414 || status_code == 500 ||
|
||||||
|
// status_code == 503);
|
||||||
|
force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!force_close && context.Request.FlushInput())
|
||||||
|
{
|
||||||
|
if (chunked && context.Response.ForceCloseChunked == false)
|
||||||
|
{
|
||||||
|
// Don't close. Keep working.
|
||||||
|
reuses++;
|
||||||
|
Unbind();
|
||||||
|
Init();
|
||||||
|
BeginReadRequest();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reuses++;
|
||||||
|
Unbind();
|
||||||
|
Init();
|
||||||
|
BeginReadRequest();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ISocket s = sock;
|
||||||
|
sock = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (s != null)
|
||||||
|
s.Shutdown(true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (s != null)
|
||||||
|
s.Close();
|
||||||
|
}
|
||||||
|
Unbind();
|
||||||
|
RemoveConnection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
299
SocketHttpListener.Portable/Net/HttpListener.cs
Normal file
299
SocketHttpListener.Portable/Net/HttpListener.cs
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Text;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
public sealed class HttpListener : IDisposable
|
||||||
|
{
|
||||||
|
internal ICryptoProvider CryptoProvider { get; private set; }
|
||||||
|
internal IStreamFactory StreamFactory { get; private set; }
|
||||||
|
internal ISocketFactory SocketFactory { get; private set; }
|
||||||
|
internal ITextEncoding TextEncoding { get; private set; }
|
||||||
|
internal IMemoryStreamFactory MemoryStreamFactory { get; private set; }
|
||||||
|
internal INetworkManager NetworkManager { get; private set; }
|
||||||
|
|
||||||
|
public bool EnableDualMode { get; set; }
|
||||||
|
|
||||||
|
AuthenticationSchemes auth_schemes;
|
||||||
|
HttpListenerPrefixCollection prefixes;
|
||||||
|
AuthenticationSchemeSelector auth_selector;
|
||||||
|
string realm;
|
||||||
|
bool ignore_write_exceptions;
|
||||||
|
bool unsafe_ntlm_auth;
|
||||||
|
bool listening;
|
||||||
|
bool disposed;
|
||||||
|
|
||||||
|
Dictionary<HttpListenerContext, HttpListenerContext> registry; // Dictionary<HttpListenerContext,HttpListenerContext>
|
||||||
|
Dictionary<HttpConnection, HttpConnection> connections;
|
||||||
|
private ILogger _logger;
|
||||||
|
private ICertificate _certificate;
|
||||||
|
|
||||||
|
public Action<HttpListenerContext> OnContext { get; set; }
|
||||||
|
|
||||||
|
public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
CryptoProvider = cryptoProvider;
|
||||||
|
StreamFactory = streamFactory;
|
||||||
|
SocketFactory = socketFactory;
|
||||||
|
NetworkManager = networkManager;
|
||||||
|
TextEncoding = textEncoding;
|
||||||
|
MemoryStreamFactory = memoryStreamFactory;
|
||||||
|
prefixes = new HttpListenerPrefixCollection(logger, this);
|
||||||
|
registry = new Dictionary<HttpListenerContext, HttpListenerContext>();
|
||||||
|
connections = new Dictionary<HttpConnection, HttpConnection>();
|
||||||
|
auth_schemes = AuthenticationSchemes.Anonymous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory)
|
||||||
|
:this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory)
|
||||||
|
: this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory)
|
||||||
|
{
|
||||||
|
_certificate = certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadCert(ICertificate cert)
|
||||||
|
{
|
||||||
|
_certificate = cert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Digest, NTLM and Negotiate require ControlPrincipal
|
||||||
|
public AuthenticationSchemes AuthenticationSchemes
|
||||||
|
{
|
||||||
|
get { return auth_schemes; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckDisposed();
|
||||||
|
auth_schemes = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate
|
||||||
|
{
|
||||||
|
get { return auth_selector; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckDisposed();
|
||||||
|
auth_selector = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IgnoreWriteExceptions
|
||||||
|
{
|
||||||
|
get { return ignore_write_exceptions; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckDisposed();
|
||||||
|
ignore_write_exceptions = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsListening
|
||||||
|
{
|
||||||
|
get { return listening; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSupported
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpListenerPrefixCollection Prefixes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
CheckDisposed();
|
||||||
|
return prefixes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use this
|
||||||
|
public string Realm
|
||||||
|
{
|
||||||
|
get { return realm; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckDisposed();
|
||||||
|
realm = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnsafeConnectionNtlmAuthentication
|
||||||
|
{
|
||||||
|
get { return unsafe_ntlm_auth; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckDisposed();
|
||||||
|
unsafe_ntlm_auth = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//internal IMonoSslStream CreateSslStream(Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback)
|
||||||
|
//{
|
||||||
|
// lock (registry)
|
||||||
|
// {
|
||||||
|
// if (tlsProvider == null)
|
||||||
|
// tlsProvider = MonoTlsProviderFactory.GetProviderInternal();
|
||||||
|
// if (tlsSettings == null)
|
||||||
|
// tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings();
|
||||||
|
// if (tlsSettings.RemoteCertificateValidationCallback == null)
|
||||||
|
// tlsSettings.RemoteCertificateValidationCallback = callback;
|
||||||
|
// return tlsProvider.CreateSslStream(innerStream, ownsStream, tlsSettings);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
internal ICertificate Certificate
|
||||||
|
{
|
||||||
|
get { return _certificate; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Abort()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!listening)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!listening)
|
||||||
|
{
|
||||||
|
disposed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Close(true);
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close(bool force)
|
||||||
|
{
|
||||||
|
CheckDisposed();
|
||||||
|
EndPointManager.RemoveListener(_logger, this);
|
||||||
|
Cleanup(force);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cleanup(bool close_existing)
|
||||||
|
{
|
||||||
|
lock (registry)
|
||||||
|
{
|
||||||
|
if (close_existing)
|
||||||
|
{
|
||||||
|
// Need to copy this since closing will call UnregisterContext
|
||||||
|
ICollection keys = registry.Keys;
|
||||||
|
var all = new HttpListenerContext[keys.Count];
|
||||||
|
keys.CopyTo(all, 0);
|
||||||
|
registry.Clear();
|
||||||
|
for (int i = all.Length - 1; i >= 0; i--)
|
||||||
|
all[i].Connection.Close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (connections)
|
||||||
|
{
|
||||||
|
ICollection keys = connections.Keys;
|
||||||
|
var conns = new HttpConnection[keys.Count];
|
||||||
|
keys.CopyTo(conns, 0);
|
||||||
|
connections.Clear();
|
||||||
|
for (int i = conns.Length - 1; i >= 0; i--)
|
||||||
|
conns[i].Close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
if (AuthenticationSchemeSelectorDelegate != null)
|
||||||
|
return AuthenticationSchemeSelectorDelegate(context.Request);
|
||||||
|
else
|
||||||
|
return auth_schemes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
CheckDisposed();
|
||||||
|
if (listening)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EndPointManager.AddListener(_logger, this);
|
||||||
|
listening = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
CheckDisposed();
|
||||||
|
listening = false;
|
||||||
|
Close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Close(true); //TODO: Should we force here or not?
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CheckDisposed()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RegisterContext(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
if (OnContext != null && IsListening)
|
||||||
|
{
|
||||||
|
OnContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (registry)
|
||||||
|
registry[context] = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void UnregisterContext(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
lock (registry)
|
||||||
|
registry.Remove(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddConnection(HttpConnection cnc)
|
||||||
|
{
|
||||||
|
lock (connections)
|
||||||
|
{
|
||||||
|
connections[cnc] = cnc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RemoveConnection(HttpConnection cnc)
|
||||||
|
{
|
||||||
|
lock (connections)
|
||||||
|
{
|
||||||
|
connections.Remove(cnc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
SocketHttpListener.Portable/Net/HttpListenerBasicIdentity.cs
Normal file
70
SocketHttpListener.Portable/Net/HttpListenerBasicIdentity.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using System.Security.Principal;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
public class HttpListenerBasicIdentity : GenericIdentity
|
||||||
|
{
|
||||||
|
string password;
|
||||||
|
|
||||||
|
public HttpListenerBasicIdentity(string username, string password)
|
||||||
|
: base(username, "Basic")
|
||||||
|
{
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string Password
|
||||||
|
{
|
||||||
|
get { return password; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GenericIdentity : IIdentity
|
||||||
|
{
|
||||||
|
private string m_name;
|
||||||
|
private string m_type;
|
||||||
|
|
||||||
|
public GenericIdentity(string name)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
throw new System.ArgumentNullException("name");
|
||||||
|
|
||||||
|
m_name = name;
|
||||||
|
m_type = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenericIdentity(string name, string type)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
throw new System.ArgumentNullException("name");
|
||||||
|
if (type == null)
|
||||||
|
throw new System.ArgumentNullException("type");
|
||||||
|
|
||||||
|
m_name = name;
|
||||||
|
m_type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string Name
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string AuthenticationType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsAuthenticated
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return !m_name.Equals("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
201
SocketHttpListener.Portable/Net/HttpListenerContext.cs
Normal file
201
SocketHttpListener.Portable/Net/HttpListenerContext.cs
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Text;
|
||||||
|
using SocketHttpListener.Net.WebSockets;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
public sealed class HttpListenerContext
|
||||||
|
{
|
||||||
|
HttpListenerRequest request;
|
||||||
|
HttpListenerResponse response;
|
||||||
|
IPrincipal user;
|
||||||
|
HttpConnection cnc;
|
||||||
|
string error;
|
||||||
|
int err_status = 400;
|
||||||
|
internal HttpListener Listener;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly ICryptoProvider _cryptoProvider;
|
||||||
|
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||||
|
private readonly ITextEncoding _textEncoding;
|
||||||
|
|
||||||
|
internal HttpListenerContext(HttpConnection cnc, ILogger logger, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
|
||||||
|
{
|
||||||
|
this.cnc = cnc;
|
||||||
|
_logger = logger;
|
||||||
|
_cryptoProvider = cryptoProvider;
|
||||||
|
_memoryStreamFactory = memoryStreamFactory;
|
||||||
|
_textEncoding = textEncoding;
|
||||||
|
request = new HttpListenerRequest(this, _textEncoding);
|
||||||
|
response = new HttpListenerResponse(this, _logger, _textEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int ErrorStatus
|
||||||
|
{
|
||||||
|
get { return err_status; }
|
||||||
|
set { err_status = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string ErrorMessage
|
||||||
|
{
|
||||||
|
get { return error; }
|
||||||
|
set { error = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool HaveError
|
||||||
|
{
|
||||||
|
get { return (error != null); }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal HttpConnection Connection
|
||||||
|
{
|
||||||
|
get { return cnc; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpListenerRequest Request
|
||||||
|
{
|
||||||
|
get { return request; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpListenerResponse Response
|
||||||
|
{
|
||||||
|
get { return response; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IPrincipal User
|
||||||
|
{
|
||||||
|
get { return user; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ParseAuthentication(AuthenticationSchemes expectedSchemes)
|
||||||
|
{
|
||||||
|
if (expectedSchemes == AuthenticationSchemes.Anonymous)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO: Handle NTLM/Digest modes
|
||||||
|
string header = request.Headers["Authorization"];
|
||||||
|
if (header == null || header.Length < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string[] authenticationData = header.Split(new char[] { ' ' }, 2);
|
||||||
|
if (string.Equals(authenticationData[0], "basic", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
user = ParseBasicAuthentication(authenticationData[1]);
|
||||||
|
}
|
||||||
|
// TODO: throw if malformed -> 400 bad request
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IPrincipal ParseBasicAuthentication(string authData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Basic AUTH Data is a formatted Base64 String
|
||||||
|
//string domain = null;
|
||||||
|
string user = null;
|
||||||
|
string password = null;
|
||||||
|
int pos = -1;
|
||||||
|
var authDataBytes = Convert.FromBase64String(authData);
|
||||||
|
string authString = _textEncoding.GetDefaultEncoding().GetString(authDataBytes, 0, authDataBytes.Length);
|
||||||
|
|
||||||
|
// The format is DOMAIN\username:password
|
||||||
|
// Domain is optional
|
||||||
|
|
||||||
|
pos = authString.IndexOf(':');
|
||||||
|
|
||||||
|
// parse the password off the end
|
||||||
|
password = authString.Substring(pos + 1);
|
||||||
|
|
||||||
|
// discard the password
|
||||||
|
authString = authString.Substring(0, pos);
|
||||||
|
|
||||||
|
// check if there is a domain
|
||||||
|
pos = authString.IndexOf('\\');
|
||||||
|
|
||||||
|
if (pos > 0)
|
||||||
|
{
|
||||||
|
//domain = authString.Substring (0, pos);
|
||||||
|
user = authString.Substring(pos);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
user = authString;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpListenerBasicIdentity identity = new HttpListenerBasicIdentity(user, password);
|
||||||
|
// TODO: What are the roles MS sets
|
||||||
|
return new GenericPrincipal(identity, new string[0]);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Invalid auth data is swallowed silently
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpListenerWebSocketContext AcceptWebSocket(string protocol)
|
||||||
|
{
|
||||||
|
if (protocol != null)
|
||||||
|
{
|
||||||
|
if (protocol.Length == 0)
|
||||||
|
throw new ArgumentException("An empty string.", "protocol");
|
||||||
|
|
||||||
|
if (!protocol.IsToken())
|
||||||
|
throw new ArgumentException("Contains an invalid character.", "protocol");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HttpListenerWebSocketContext(this, protocol, _cryptoProvider, _memoryStreamFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GenericPrincipal : IPrincipal
|
||||||
|
{
|
||||||
|
private IIdentity m_identity;
|
||||||
|
private string[] m_roles;
|
||||||
|
|
||||||
|
public GenericPrincipal(IIdentity identity, string[] roles)
|
||||||
|
{
|
||||||
|
if (identity == null)
|
||||||
|
throw new ArgumentNullException("identity");
|
||||||
|
|
||||||
|
m_identity = identity;
|
||||||
|
if (roles != null)
|
||||||
|
{
|
||||||
|
m_roles = new string[roles.Length];
|
||||||
|
for (int i = 0; i < roles.Length; ++i)
|
||||||
|
{
|
||||||
|
m_roles[i] = roles[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_roles = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IIdentity Identity
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_identity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsInRole(string role)
|
||||||
|
{
|
||||||
|
if (role == null || m_roles == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_roles.Length; ++i)
|
||||||
|
{
|
||||||
|
if (m_roles[i] != null && String.Compare(m_roles[i], role, StringComparison.OrdinalIgnoreCase) == 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
public class HttpListenerPrefixCollection : ICollection<string>, IEnumerable<string>, IEnumerable
|
||||||
|
{
|
||||||
|
List<string> prefixes = new List<string>();
|
||||||
|
HttpListener listener;
|
||||||
|
|
||||||
|
private ILogger _logger;
|
||||||
|
|
||||||
|
internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get { return prefixes.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReadOnly
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSynchronized
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(string uriPrefix)
|
||||||
|
{
|
||||||
|
listener.CheckDisposed();
|
||||||
|
ListenerPrefix.CheckUri(uriPrefix);
|
||||||
|
if (prefixes.Contains(uriPrefix))
|
||||||
|
return;
|
||||||
|
|
||||||
|
prefixes.Add(uriPrefix);
|
||||||
|
if (listener.IsListening)
|
||||||
|
EndPointManager.AddPrefix(_logger, uriPrefix, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
listener.CheckDisposed();
|
||||||
|
prefixes.Clear();
|
||||||
|
if (listener.IsListening)
|
||||||
|
EndPointManager.RemoveListener(_logger, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(string uriPrefix)
|
||||||
|
{
|
||||||
|
listener.CheckDisposed();
|
||||||
|
return prefixes.Contains(uriPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(string[] array, int offset)
|
||||||
|
{
|
||||||
|
listener.CheckDisposed();
|
||||||
|
prefixes.CopyTo(array, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(Array array, int offset)
|
||||||
|
{
|
||||||
|
listener.CheckDisposed();
|
||||||
|
((ICollection)prefixes).CopyTo(array, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<string> GetEnumerator()
|
||||||
|
{
|
||||||
|
return prefixes.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return prefixes.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(string uriPrefix)
|
||||||
|
{
|
||||||
|
listener.CheckDisposed();
|
||||||
|
if (uriPrefix == null)
|
||||||
|
throw new ArgumentNullException("uriPrefix");
|
||||||
|
|
||||||
|
bool result = prefixes.Remove(uriPrefix);
|
||||||
|
if (result && listener.IsListening)
|
||||||
|
EndPointManager.RemovePrefix(_logger, uriPrefix, listener);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
654
SocketHttpListener.Portable/Net/HttpListenerRequest.cs
Normal file
654
SocketHttpListener.Portable/Net/HttpListenerRequest.cs
Normal file
@ -0,0 +1,654 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
using MediaBrowser.Model.Text;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
public sealed class HttpListenerRequest
|
||||||
|
{
|
||||||
|
string[] accept_types;
|
||||||
|
Encoding content_encoding;
|
||||||
|
long content_length;
|
||||||
|
bool cl_set;
|
||||||
|
CookieCollection cookies;
|
||||||
|
WebHeaderCollection headers;
|
||||||
|
string method;
|
||||||
|
Stream input_stream;
|
||||||
|
Version version;
|
||||||
|
QueryParamCollection query_string; // check if null is ok, check if read-only, check case-sensitiveness
|
||||||
|
string raw_url;
|
||||||
|
Uri url;
|
||||||
|
Uri referrer;
|
||||||
|
string[] user_languages;
|
||||||
|
HttpListenerContext context;
|
||||||
|
bool is_chunked;
|
||||||
|
bool ka_set;
|
||||||
|
bool keep_alive;
|
||||||
|
|
||||||
|
private readonly ITextEncoding _textEncoding;
|
||||||
|
|
||||||
|
internal HttpListenerRequest(HttpListenerContext context, ITextEncoding textEncoding)
|
||||||
|
{
|
||||||
|
this.context = context;
|
||||||
|
_textEncoding = textEncoding;
|
||||||
|
headers = new WebHeaderCollection();
|
||||||
|
version = HttpVersion.Version10;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char[] separators = new char[] { ' ' };
|
||||||
|
|
||||||
|
internal void SetRequestLine(string req)
|
||||||
|
{
|
||||||
|
string[] parts = req.Split(separators, 3);
|
||||||
|
if (parts.Length != 3)
|
||||||
|
{
|
||||||
|
context.ErrorMessage = "Invalid request line (parts).";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
method = parts[0];
|
||||||
|
foreach (char c in method)
|
||||||
|
{
|
||||||
|
int ic = (int)c;
|
||||||
|
|
||||||
|
if ((ic >= 'A' && ic <= 'Z') ||
|
||||||
|
(ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' &&
|
||||||
|
c != '<' && c != '>' && c != '@' && c != ',' && c != ';' &&
|
||||||
|
c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' &&
|
||||||
|
c != ']' && c != '?' && c != '=' && c != '{' && c != '}'))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
context.ErrorMessage = "(Invalid verb)";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
raw_url = parts[1];
|
||||||
|
if (parts[2].Length != 8 || !parts[2].StartsWith("HTTP/"))
|
||||||
|
{
|
||||||
|
context.ErrorMessage = "Invalid request line (version).";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
version = new Version(parts[2].Substring(5));
|
||||||
|
if (version.Major < 1)
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
context.ErrorMessage = "Invalid request line (version).";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateQueryString(string query)
|
||||||
|
{
|
||||||
|
if (query == null || query.Length == 0)
|
||||||
|
{
|
||||||
|
query_string = new QueryParamCollection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
query_string = new QueryParamCollection();
|
||||||
|
if (query[0] == '?')
|
||||||
|
query = query.Substring(1);
|
||||||
|
string[] components = query.Split('&');
|
||||||
|
foreach (string kv in components)
|
||||||
|
{
|
||||||
|
int pos = kv.IndexOf('=');
|
||||||
|
if (pos == -1)
|
||||||
|
{
|
||||||
|
query_string.Add(null, WebUtility.UrlDecode(kv));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string key = WebUtility.UrlDecode(kv.Substring(0, pos));
|
||||||
|
string val = WebUtility.UrlDecode(kv.Substring(pos + 1));
|
||||||
|
|
||||||
|
query_string.Add(key, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void FinishInitialization()
|
||||||
|
{
|
||||||
|
string host = UserHostName;
|
||||||
|
if (version > HttpVersion.Version10 && (host == null || host.Length == 0))
|
||||||
|
{
|
||||||
|
context.ErrorMessage = "Invalid host name";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string path;
|
||||||
|
Uri raw_uri = null;
|
||||||
|
if (MaybeUri(raw_url.ToLowerInvariant()) && Uri.TryCreate(raw_url, UriKind.Absolute, out raw_uri))
|
||||||
|
path = raw_uri.PathAndQuery;
|
||||||
|
else
|
||||||
|
path = raw_url;
|
||||||
|
|
||||||
|
if ((host == null || host.Length == 0))
|
||||||
|
host = UserHostAddress;
|
||||||
|
|
||||||
|
if (raw_uri != null)
|
||||||
|
host = raw_uri.Host;
|
||||||
|
|
||||||
|
int colon = host.LastIndexOf(':');
|
||||||
|
if (colon >= 0)
|
||||||
|
host = host.Substring(0, colon);
|
||||||
|
|
||||||
|
string base_uri = String.Format("{0}://{1}:{2}",
|
||||||
|
(IsSecureConnection) ? (IsWebSocketRequest ? "wss" : "https") : (IsWebSocketRequest ? "ws" : "http"),
|
||||||
|
host, LocalEndPoint.Port);
|
||||||
|
|
||||||
|
if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out url))
|
||||||
|
{
|
||||||
|
context.ErrorMessage = WebUtility.HtmlEncode("Invalid url: " + base_uri + path);
|
||||||
|
return; return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateQueryString(url.Query);
|
||||||
|
|
||||||
|
if (version >= HttpVersion.Version11)
|
||||||
|
{
|
||||||
|
string t_encoding = Headers["Transfer-Encoding"];
|
||||||
|
is_chunked = (t_encoding != null && String.Compare(t_encoding, "chunked", StringComparison.OrdinalIgnoreCase) == 0);
|
||||||
|
// 'identity' is not valid!
|
||||||
|
if (t_encoding != null && !is_chunked)
|
||||||
|
{
|
||||||
|
context.Connection.SendError(null, 501);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_chunked && !cl_set)
|
||||||
|
{
|
||||||
|
if (String.Compare(method, "POST", StringComparison.OrdinalIgnoreCase) == 0 ||
|
||||||
|
String.Compare(method, "PUT", StringComparison.OrdinalIgnoreCase) == 0)
|
||||||
|
{
|
||||||
|
context.Connection.SendError(null, 411);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.Compare(Headers["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0)
|
||||||
|
{
|
||||||
|
ResponseStream output = context.Connection.GetResponseStream();
|
||||||
|
|
||||||
|
var _100continue = _textEncoding.GetASCIIEncoding().GetBytes("HTTP/1.1 100 Continue\r\n\r\n");
|
||||||
|
|
||||||
|
output.InternalWrite(_100continue, 0, _100continue.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool MaybeUri(string s)
|
||||||
|
{
|
||||||
|
int p = s.IndexOf(':');
|
||||||
|
if (p == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (p >= 10)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return IsPredefinedScheme(s.Substring(0, p));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Using a simple block of if's is twice as slow as the compiler generated
|
||||||
|
// switch statement. But using this tuned code is faster than the
|
||||||
|
// compiler generated code, with a million loops on x86-64:
|
||||||
|
//
|
||||||
|
// With "http": .10 vs .51 (first check)
|
||||||
|
// with "https": .16 vs .51 (second check)
|
||||||
|
// with "foo": .22 vs .31 (never found)
|
||||||
|
// with "mailto": .12 vs .51 (last check)
|
||||||
|
//
|
||||||
|
//
|
||||||
|
static bool IsPredefinedScheme(string scheme)
|
||||||
|
{
|
||||||
|
if (scheme == null || scheme.Length < 3)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char c = scheme[0];
|
||||||
|
if (c == 'h')
|
||||||
|
return (scheme == "http" || scheme == "https");
|
||||||
|
if (c == 'f')
|
||||||
|
return (scheme == "file" || scheme == "ftp");
|
||||||
|
|
||||||
|
if (c == 'n')
|
||||||
|
{
|
||||||
|
c = scheme[1];
|
||||||
|
if (c == 'e')
|
||||||
|
return (scheme == "news" || scheme == "net.pipe" || scheme == "net.tcp");
|
||||||
|
if (scheme == "nntp")
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((c == 'g' && scheme == "gopher") || (c == 'm' && scheme == "mailto"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string Unquote(String str)
|
||||||
|
{
|
||||||
|
int start = str.IndexOf('\"');
|
||||||
|
int end = str.LastIndexOf('\"');
|
||||||
|
if (start >= 0 && end >= 0)
|
||||||
|
str = str.Substring(start + 1, end - 1);
|
||||||
|
return str.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddHeader(string header)
|
||||||
|
{
|
||||||
|
int colon = header.IndexOf(':');
|
||||||
|
if (colon == -1 || colon == 0)
|
||||||
|
{
|
||||||
|
context.ErrorMessage = "Bad Request";
|
||||||
|
context.ErrorStatus = 400;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string name = header.Substring(0, colon).Trim();
|
||||||
|
string val = header.Substring(colon + 1).Trim();
|
||||||
|
string lower = name.ToLowerInvariant();
|
||||||
|
headers.SetInternal(name, val);
|
||||||
|
switch (lower)
|
||||||
|
{
|
||||||
|
case "accept-language":
|
||||||
|
user_languages = val.Split(','); // yes, only split with a ','
|
||||||
|
break;
|
||||||
|
case "accept":
|
||||||
|
accept_types = val.Split(','); // yes, only split with a ','
|
||||||
|
break;
|
||||||
|
case "content-length":
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//TODO: max. content_length?
|
||||||
|
content_length = Int64.Parse(val.Trim());
|
||||||
|
if (content_length < 0)
|
||||||
|
context.ErrorMessage = "Invalid Content-Length.";
|
||||||
|
cl_set = true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
context.ErrorMessage = "Invalid Content-Length.";
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "content-type":
|
||||||
|
{
|
||||||
|
var contents = val.Split(';');
|
||||||
|
foreach (var content in contents)
|
||||||
|
{
|
||||||
|
var tmp = content.Trim();
|
||||||
|
if (tmp.StartsWith("charset"))
|
||||||
|
{
|
||||||
|
var charset = tmp.GetValue("=");
|
||||||
|
if (charset != null && charset.Length > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
// Support upnp/dlna devices - CONTENT-TYPE: text/xml ; charset="utf-8"\r\n
|
||||||
|
charset = charset.Trim('"');
|
||||||
|
var index = charset.IndexOf('"');
|
||||||
|
if (index != -1) charset = charset.Substring(0, index);
|
||||||
|
|
||||||
|
content_encoding = Encoding.GetEncoding(charset);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
context.ErrorMessage = "Invalid Content-Type header: " + charset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "referer":
|
||||||
|
try
|
||||||
|
{
|
||||||
|
referrer = new Uri(val);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
referrer = new Uri("http://someone.is.screwing.with.the.headers.com/");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "cookie":
|
||||||
|
if (cookies == null)
|
||||||
|
cookies = new CookieCollection();
|
||||||
|
|
||||||
|
string[] cookieStrings = val.Split(new char[] { ',', ';' });
|
||||||
|
Cookie current = null;
|
||||||
|
int version = 0;
|
||||||
|
foreach (string cookieString in cookieStrings)
|
||||||
|
{
|
||||||
|
string str = cookieString.Trim();
|
||||||
|
if (str.Length == 0)
|
||||||
|
continue;
|
||||||
|
if (str.StartsWith("$Version"))
|
||||||
|
{
|
||||||
|
version = Int32.Parse(Unquote(str.Substring(str.IndexOf('=') + 1)));
|
||||||
|
}
|
||||||
|
else if (str.StartsWith("$Path"))
|
||||||
|
{
|
||||||
|
if (current != null)
|
||||||
|
current.Path = str.Substring(str.IndexOf('=') + 1).Trim();
|
||||||
|
}
|
||||||
|
else if (str.StartsWith("$Domain"))
|
||||||
|
{
|
||||||
|
if (current != null)
|
||||||
|
current.Domain = str.Substring(str.IndexOf('=') + 1).Trim();
|
||||||
|
}
|
||||||
|
else if (str.StartsWith("$Port"))
|
||||||
|
{
|
||||||
|
if (current != null)
|
||||||
|
current.Port = str.Substring(str.IndexOf('=') + 1).Trim();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (current != null)
|
||||||
|
{
|
||||||
|
cookies.Add(current);
|
||||||
|
}
|
||||||
|
current = new Cookie();
|
||||||
|
int idx = str.IndexOf('=');
|
||||||
|
if (idx > 0)
|
||||||
|
{
|
||||||
|
current.Name = str.Substring(0, idx).Trim();
|
||||||
|
current.Value = str.Substring(idx + 1).Trim();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current.Name = str.Trim();
|
||||||
|
current.Value = String.Empty;
|
||||||
|
}
|
||||||
|
current.Version = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current != null)
|
||||||
|
{
|
||||||
|
cookies.Add(current);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true is the stream could be reused.
|
||||||
|
internal bool FlushInput()
|
||||||
|
{
|
||||||
|
if (!HasEntityBody)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
int length = 2048;
|
||||||
|
if (content_length > 0)
|
||||||
|
length = (int)Math.Min(content_length, (long)length);
|
||||||
|
|
||||||
|
byte[] bytes = new byte[length];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// TODO: test if MS has a timeout when doing this
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var task = InputStream.ReadAsync(bytes, 0, length);
|
||||||
|
var result = Task.WaitAll(new [] { task }, 1000);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (task.Result <= 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException e)
|
||||||
|
{
|
||||||
|
input_stream = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] AcceptTypes
|
||||||
|
{
|
||||||
|
get { return accept_types; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ClientCertificateError
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HttpConnection cnc = context.Connection;
|
||||||
|
//if (cnc.ClientCertificate == null)
|
||||||
|
// throw new InvalidOperationException("No client certificate");
|
||||||
|
//int[] errors = cnc.ClientCertificateErrors;
|
||||||
|
//if (errors != null && errors.Length > 0)
|
||||||
|
// return errors[0];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Encoding ContentEncoding
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (content_encoding == null)
|
||||||
|
content_encoding = _textEncoding.GetDefaultEncoding();
|
||||||
|
return content_encoding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long ContentLength64
|
||||||
|
{
|
||||||
|
get { return content_length; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ContentType
|
||||||
|
{
|
||||||
|
get { return headers["content-type"]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CookieCollection Cookies
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// TODO: check if the collection is read-only
|
||||||
|
if (cookies == null)
|
||||||
|
cookies = new CookieCollection();
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasEntityBody
|
||||||
|
{
|
||||||
|
get { return (content_length > 0 || is_chunked); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryParamCollection Headers
|
||||||
|
{
|
||||||
|
get { return headers; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string HttpMethod
|
||||||
|
{
|
||||||
|
get { return method; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream InputStream
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (input_stream == null)
|
||||||
|
{
|
||||||
|
if (is_chunked || content_length > 0)
|
||||||
|
input_stream = context.Connection.GetRequestStream(is_chunked, content_length);
|
||||||
|
else
|
||||||
|
input_stream = Stream.Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return input_stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAuthenticated
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLocal
|
||||||
|
{
|
||||||
|
get { return RemoteEndPoint.IpAddress.Equals(IpAddressInfo.Loopback) || RemoteEndPoint.IpAddress.Equals(IpAddressInfo.IPv6Loopback) || LocalEndPoint.IpAddress.Equals(RemoteEndPoint.IpAddress); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSecureConnection
|
||||||
|
{
|
||||||
|
get { return context.Connection.IsSecure; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool KeepAlive
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (ka_set)
|
||||||
|
return keep_alive;
|
||||||
|
|
||||||
|
ka_set = true;
|
||||||
|
// 1. Connection header
|
||||||
|
// 2. Protocol (1.1 == keep-alive by default)
|
||||||
|
// 3. Keep-Alive header
|
||||||
|
string cnc = headers["Connection"];
|
||||||
|
if (!String.IsNullOrEmpty(cnc))
|
||||||
|
{
|
||||||
|
keep_alive = (0 == String.Compare(cnc, "keep-alive", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
else if (version == HttpVersion.Version11)
|
||||||
|
{
|
||||||
|
keep_alive = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cnc = headers["keep-alive"];
|
||||||
|
if (!String.IsNullOrEmpty(cnc))
|
||||||
|
keep_alive = (0 != String.Compare(cnc, "closed", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
return keep_alive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IpEndPointInfo LocalEndPoint
|
||||||
|
{
|
||||||
|
get { return context.Connection.LocalEndPoint; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Version ProtocolVersion
|
||||||
|
{
|
||||||
|
get { return version; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryParamCollection QueryString
|
||||||
|
{
|
||||||
|
get { return query_string; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RawUrl
|
||||||
|
{
|
||||||
|
get { return raw_url; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IpEndPointInfo RemoteEndPoint
|
||||||
|
{
|
||||||
|
get { return context.Connection.RemoteEndPoint; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid RequestTraceIdentifier
|
||||||
|
{
|
||||||
|
get { return Guid.Empty; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri Url
|
||||||
|
{
|
||||||
|
get { return url; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri UrlReferrer
|
||||||
|
{
|
||||||
|
get { return referrer; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UserAgent
|
||||||
|
{
|
||||||
|
get { return headers["user-agent"]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UserHostAddress
|
||||||
|
{
|
||||||
|
get { return LocalEndPoint.ToString(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UserHostName
|
||||||
|
{
|
||||||
|
get { return headers["host"]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] UserLanguages
|
||||||
|
{
|
||||||
|
get { return user_languages; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ServiceName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _websocketRequestWasSet;
|
||||||
|
private bool _websocketRequest;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the request is a WebSocket connection request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public bool IsWebSocketRequest
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!_websocketRequestWasSet)
|
||||||
|
{
|
||||||
|
_websocketRequest = method == "GET" &&
|
||||||
|
version > HttpVersion.Version10 &&
|
||||||
|
headers.Contains("Upgrade", "websocket") &&
|
||||||
|
headers.Contains("Connection", "Upgrade");
|
||||||
|
|
||||||
|
_websocketRequestWasSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _websocketRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ICertificate> GetClientCertificateAsync()
|
||||||
|
{
|
||||||
|
return Task.FromResult<ICertificate>(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
517
SocketHttpListener.Portable/Net/HttpListenerResponse.cs
Normal file
517
SocketHttpListener.Portable/Net/HttpListenerResponse.cs
Normal file
@ -0,0 +1,517 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Text;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
public sealed class HttpListenerResponse : IDisposable
|
||||||
|
{
|
||||||
|
bool disposed;
|
||||||
|
Encoding content_encoding;
|
||||||
|
long content_length;
|
||||||
|
bool cl_set;
|
||||||
|
string content_type;
|
||||||
|
CookieCollection cookies;
|
||||||
|
WebHeaderCollection headers = new WebHeaderCollection();
|
||||||
|
bool keep_alive = true;
|
||||||
|
ResponseStream output_stream;
|
||||||
|
Version version = HttpVersion.Version11;
|
||||||
|
string location;
|
||||||
|
int status_code = 200;
|
||||||
|
string status_description = "OK";
|
||||||
|
bool chunked;
|
||||||
|
HttpListenerContext context;
|
||||||
|
|
||||||
|
internal bool HeadersSent;
|
||||||
|
internal object headers_lock = new object();
|
||||||
|
|
||||||
|
bool force_close_chunked;
|
||||||
|
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly ITextEncoding _textEncoding;
|
||||||
|
|
||||||
|
internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding)
|
||||||
|
{
|
||||||
|
this.context = context;
|
||||||
|
_logger = logger;
|
||||||
|
_textEncoding = textEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool CloseConnection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return headers["Connection"] == "close";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool ForceCloseChunked
|
||||||
|
{
|
||||||
|
get { return force_close_chunked; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Encoding ContentEncoding
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (content_encoding == null)
|
||||||
|
content_encoding = _textEncoding.GetDefaultEncoding();
|
||||||
|
return content_encoding;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
content_encoding = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long ContentLength64
|
||||||
|
{
|
||||||
|
get { return content_length; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
if (HeadersSent)
|
||||||
|
throw new InvalidOperationException("Cannot be changed after headers are sent.");
|
||||||
|
|
||||||
|
if (value < 0)
|
||||||
|
throw new ArgumentOutOfRangeException("Must be >= 0", "value");
|
||||||
|
|
||||||
|
cl_set = true;
|
||||||
|
content_length = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ContentType
|
||||||
|
{
|
||||||
|
get { return content_type; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
// TODO: is null ok?
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
content_type = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 2109, 2965 + the netscape specification at http://wp.netscape.com/newsref/std/cookie_spec.html
|
||||||
|
public CookieCollection Cookies
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (cookies == null)
|
||||||
|
cookies = new CookieCollection();
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
set { cookies = value; } // null allowed?
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebHeaderCollection Headers
|
||||||
|
{
|
||||||
|
get { return headers; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* "If you attempt to set a Content-Length, Keep-Alive, Transfer-Encoding, or
|
||||||
|
* WWW-Authenticate header using the Headers property, an exception will be
|
||||||
|
* thrown. Use the KeepAlive or ContentLength64 properties to set these headers.
|
||||||
|
* You cannot set the Transfer-Encoding or WWW-Authenticate headers manually."
|
||||||
|
*/
|
||||||
|
// TODO: check if this is marked readonly after headers are sent.
|
||||||
|
headers = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool KeepAlive
|
||||||
|
{
|
||||||
|
get { return keep_alive; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
keep_alive = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream OutputStream
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (output_stream == null)
|
||||||
|
output_stream = context.Connection.GetResponseStream();
|
||||||
|
return output_stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Version ProtocolVersion
|
||||||
|
{
|
||||||
|
get { return version; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
if (value == null)
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
|
||||||
|
if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
|
||||||
|
throw new ArgumentException("Must be 1.0 or 1.1", "value");
|
||||||
|
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
version = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RedirectLocation
|
||||||
|
{
|
||||||
|
get { return location; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
location = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SendChunked
|
||||||
|
{
|
||||||
|
get { return chunked; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
chunked = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int StatusCode
|
||||||
|
{
|
||||||
|
get { return status_code; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
if (value < 100 || value > 999)
|
||||||
|
throw new ProtocolViolationException("StatusCode must be between 100 and 999.");
|
||||||
|
status_code = value;
|
||||||
|
status_description = GetStatusDescription(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetStatusDescription(int code)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case 100: return "Continue";
|
||||||
|
case 101: return "Switching Protocols";
|
||||||
|
case 102: return "Processing";
|
||||||
|
case 200: return "OK";
|
||||||
|
case 201: return "Created";
|
||||||
|
case 202: return "Accepted";
|
||||||
|
case 203: return "Non-Authoritative Information";
|
||||||
|
case 204: return "No Content";
|
||||||
|
case 205: return "Reset Content";
|
||||||
|
case 206: return "Partial Content";
|
||||||
|
case 207: return "Multi-Status";
|
||||||
|
case 300: return "Multiple Choices";
|
||||||
|
case 301: return "Moved Permanently";
|
||||||
|
case 302: return "Found";
|
||||||
|
case 303: return "See Other";
|
||||||
|
case 304: return "Not Modified";
|
||||||
|
case 305: return "Use Proxy";
|
||||||
|
case 307: return "Temporary Redirect";
|
||||||
|
case 400: return "Bad Request";
|
||||||
|
case 401: return "Unauthorized";
|
||||||
|
case 402: return "Payment Required";
|
||||||
|
case 403: return "Forbidden";
|
||||||
|
case 404: return "Not Found";
|
||||||
|
case 405: return "Method Not Allowed";
|
||||||
|
case 406: return "Not Acceptable";
|
||||||
|
case 407: return "Proxy Authentication Required";
|
||||||
|
case 408: return "Request Timeout";
|
||||||
|
case 409: return "Conflict";
|
||||||
|
case 410: return "Gone";
|
||||||
|
case 411: return "Length Required";
|
||||||
|
case 412: return "Precondition Failed";
|
||||||
|
case 413: return "Request Entity Too Large";
|
||||||
|
case 414: return "Request-Uri Too Long";
|
||||||
|
case 415: return "Unsupported Media Type";
|
||||||
|
case 416: return "Requested Range Not Satisfiable";
|
||||||
|
case 417: return "Expectation Failed";
|
||||||
|
case 422: return "Unprocessable Entity";
|
||||||
|
case 423: return "Locked";
|
||||||
|
case 424: return "Failed Dependency";
|
||||||
|
case 500: return "Internal Server Error";
|
||||||
|
case 501: return "Not Implemented";
|
||||||
|
case 502: return "Bad Gateway";
|
||||||
|
case 503: return "Service Unavailable";
|
||||||
|
case 504: return "Gateway Timeout";
|
||||||
|
case 505: return "Http Version Not Supported";
|
||||||
|
case 507: return "Insufficient Storage";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StatusDescription
|
||||||
|
{
|
||||||
|
get { return status_description; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
status_description = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
Close(true); //TODO: Abort or Close?
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Abort()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddHeader(string name, string value)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
|
||||||
|
if (name == "")
|
||||||
|
throw new ArgumentException("'name' cannot be empty", "name");
|
||||||
|
|
||||||
|
//TODO: check for forbidden headers and invalid characters
|
||||||
|
if (value.Length > 65535)
|
||||||
|
throw new ArgumentOutOfRangeException("value");
|
||||||
|
|
||||||
|
headers.Set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendCookie(Cookie cookie)
|
||||||
|
{
|
||||||
|
if (cookie == null)
|
||||||
|
throw new ArgumentNullException("cookie");
|
||||||
|
|
||||||
|
Cookies.Add(cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendHeader(string name, string value)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
|
||||||
|
if (name == "")
|
||||||
|
throw new ArgumentException("'name' cannot be empty", "name");
|
||||||
|
|
||||||
|
if (value.Length > 65535)
|
||||||
|
throw new ArgumentOutOfRangeException("value");
|
||||||
|
|
||||||
|
headers.Add(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close(bool force)
|
||||||
|
{
|
||||||
|
if (force)
|
||||||
|
{
|
||||||
|
_logger.Debug("HttpListenerResponse force closing HttpConnection");
|
||||||
|
}
|
||||||
|
disposed = true;
|
||||||
|
context.Connection.Close(force);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close(byte[] responseEntity, bool willBlock)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (responseEntity == null)
|
||||||
|
throw new ArgumentNullException("responseEntity");
|
||||||
|
|
||||||
|
//TODO: if willBlock -> BeginWrite + Close ?
|
||||||
|
ContentLength64 = responseEntity.Length;
|
||||||
|
OutputStream.Write(responseEntity, 0, (int)content_length);
|
||||||
|
Close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Redirect(string url)
|
||||||
|
{
|
||||||
|
StatusCode = 302; // Found
|
||||||
|
location = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FindCookie(Cookie cookie)
|
||||||
|
{
|
||||||
|
string name = cookie.Name;
|
||||||
|
string domain = cookie.Domain;
|
||||||
|
string path = cookie.Path;
|
||||||
|
foreach (Cookie c in cookies)
|
||||||
|
{
|
||||||
|
if (name != c.Name)
|
||||||
|
continue;
|
||||||
|
if (domain != c.Domain)
|
||||||
|
continue;
|
||||||
|
if (path == c.Path)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SendHeaders(bool closing, MemoryStream ms)
|
||||||
|
{
|
||||||
|
Encoding encoding = content_encoding;
|
||||||
|
if (encoding == null)
|
||||||
|
encoding = _textEncoding.GetDefaultEncoding();
|
||||||
|
|
||||||
|
if (content_type != null)
|
||||||
|
{
|
||||||
|
if (content_encoding != null && content_type.IndexOf("charset=", StringComparison.Ordinal) == -1)
|
||||||
|
{
|
||||||
|
string enc_name = content_encoding.WebName;
|
||||||
|
headers.SetInternal("Content-Type", content_type + "; charset=" + enc_name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
headers.SetInternal("Content-Type", content_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers["Server"] == null)
|
||||||
|
headers.SetInternal("Server", "Mono-HTTPAPI/1.0");
|
||||||
|
|
||||||
|
CultureInfo inv = CultureInfo.InvariantCulture;
|
||||||
|
if (headers["Date"] == null)
|
||||||
|
headers.SetInternal("Date", DateTime.UtcNow.ToString("r", inv));
|
||||||
|
|
||||||
|
if (!chunked)
|
||||||
|
{
|
||||||
|
if (!cl_set && closing)
|
||||||
|
{
|
||||||
|
cl_set = true;
|
||||||
|
content_length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cl_set)
|
||||||
|
headers.SetInternal("Content-Length", content_length.ToString(inv));
|
||||||
|
}
|
||||||
|
|
||||||
|
Version v = context.Request.ProtocolVersion;
|
||||||
|
if (!cl_set && !chunked && v >= HttpVersion.Version11)
|
||||||
|
chunked = true;
|
||||||
|
|
||||||
|
/* Apache forces closing the connection for these status codes:
|
||||||
|
* HttpStatusCode.BadRequest 400
|
||||||
|
* HttpStatusCode.RequestTimeout 408
|
||||||
|
* HttpStatusCode.LengthRequired 411
|
||||||
|
* HttpStatusCode.RequestEntityTooLarge 413
|
||||||
|
* HttpStatusCode.RequestUriTooLong 414
|
||||||
|
* HttpStatusCode.InternalServerError 500
|
||||||
|
* HttpStatusCode.ServiceUnavailable 503
|
||||||
|
*/
|
||||||
|
bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
|
||||||
|
status_code == 413 || status_code == 414 || status_code == 500 ||
|
||||||
|
status_code == 503);
|
||||||
|
|
||||||
|
if (conn_close == false)
|
||||||
|
conn_close = !context.Request.KeepAlive;
|
||||||
|
|
||||||
|
// They sent both KeepAlive: true and Connection: close!?
|
||||||
|
if (!keep_alive || conn_close)
|
||||||
|
{
|
||||||
|
headers.SetInternal("Connection", "close");
|
||||||
|
conn_close = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunked)
|
||||||
|
headers.SetInternal("Transfer-Encoding", "chunked");
|
||||||
|
|
||||||
|
//int reuses = context.Connection.Reuses;
|
||||||
|
//if (reuses >= 100)
|
||||||
|
//{
|
||||||
|
// _logger.Debug("HttpListenerResponse - keep alive has exceeded 100 uses and will be closed.");
|
||||||
|
|
||||||
|
// force_close_chunked = true;
|
||||||
|
// if (!conn_close)
|
||||||
|
// {
|
||||||
|
// headers.SetInternal("Connection", "close");
|
||||||
|
// conn_close = true;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (!conn_close)
|
||||||
|
{
|
||||||
|
if (context.Request.ProtocolVersion <= HttpVersion.Version10)
|
||||||
|
headers.SetInternal("Connection", "keep-alive");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location != null)
|
||||||
|
headers.SetInternal("Location", location);
|
||||||
|
|
||||||
|
if (cookies != null)
|
||||||
|
{
|
||||||
|
foreach (Cookie cookie in cookies)
|
||||||
|
headers.SetInternal("Set-Cookie", cookie.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
using (StreamWriter writer = new StreamWriter(ms, encoding, 256, true))
|
||||||
|
{
|
||||||
|
writer.Write("HTTP/{0} {1} {2}\r\n", version, status_code, status_description);
|
||||||
|
string headers_str = headers.ToStringMultiValue();
|
||||||
|
writer.Write(headers_str);
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
int preamble = encoding.GetPreamble().Length;
|
||||||
|
if (output_stream == null)
|
||||||
|
output_stream = context.Connection.GetResponseStream();
|
||||||
|
|
||||||
|
/* Assumes that the ms was at position 0 */
|
||||||
|
ms.Position = preamble;
|
||||||
|
HeadersSent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCookie(Cookie cookie)
|
||||||
|
{
|
||||||
|
if (cookie == null)
|
||||||
|
throw new ArgumentNullException("cookie");
|
||||||
|
|
||||||
|
if (cookies != null)
|
||||||
|
{
|
||||||
|
if (FindCookie(cookie))
|
||||||
|
throw new ArgumentException("The cookie already exists.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cookies = new CookieCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
cookies.Add(cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
321
SocketHttpListener.Portable/Net/HttpStatusCode.cs
Normal file
321
SocketHttpListener.Portable/Net/HttpStatusCode.cs
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the values of the HTTP status codes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The HttpStatusCode enumeration contains the values of the HTTP status codes defined in
|
||||||
|
/// <see href="http://tools.ietf.org/html/rfc2616#section-10">RFC 2616</see> for HTTP 1.1.
|
||||||
|
/// </remarks>
|
||||||
|
public enum HttpStatusCode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 100.
|
||||||
|
/// Indicates that the client should continue with its request.
|
||||||
|
/// </summary>
|
||||||
|
Continue = 100,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 101.
|
||||||
|
/// Indicates that the server is switching the HTTP version or protocol on the connection.
|
||||||
|
/// </summary>
|
||||||
|
SwitchingProtocols = 101,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 200.
|
||||||
|
/// Indicates that the client's request has succeeded.
|
||||||
|
/// </summary>
|
||||||
|
OK = 200,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 201.
|
||||||
|
/// Indicates that the client's request has been fulfilled and resulted in a new resource being
|
||||||
|
/// created.
|
||||||
|
/// </summary>
|
||||||
|
Created = 201,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 202.
|
||||||
|
/// Indicates that the client's request has been accepted for processing, but the processing
|
||||||
|
/// hasn't been completed.
|
||||||
|
/// </summary>
|
||||||
|
Accepted = 202,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 203.
|
||||||
|
/// Indicates that the returned metainformation is from a local or a third-party copy instead of
|
||||||
|
/// the origin server.
|
||||||
|
/// </summary>
|
||||||
|
NonAuthoritativeInformation = 203,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 204.
|
||||||
|
/// Indicates that the server has fulfilled the client's request but doesn't need to return
|
||||||
|
/// an entity-body.
|
||||||
|
/// </summary>
|
||||||
|
NoContent = 204,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 205.
|
||||||
|
/// Indicates that the server has fulfilled the client's request, and the user agent should
|
||||||
|
/// reset the document view which caused the request to be sent.
|
||||||
|
/// </summary>
|
||||||
|
ResetContent = 205,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 206.
|
||||||
|
/// Indicates that the server has fulfilled the partial GET request for the resource.
|
||||||
|
/// </summary>
|
||||||
|
PartialContent = 206,
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Equivalent to status code 300.
|
||||||
|
/// Indicates that the requested resource corresponds to any of multiple representations.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// MultipleChoices is a synonym for Ambiguous.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
MultipleChoices = 300,
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Equivalent to status code 300.
|
||||||
|
/// Indicates that the requested resource corresponds to any of multiple representations.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Ambiguous is a synonym for MultipleChoices.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
Ambiguous = 300,
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Equivalent to status code 301.
|
||||||
|
/// Indicates that the requested resource has been assigned a new permanent URI and
|
||||||
|
/// any future references to this resource should use one of the returned URIs.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// MovedPermanently is a synonym for Moved.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
MovedPermanently = 301,
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Equivalent to status code 301.
|
||||||
|
/// Indicates that the requested resource has been assigned a new permanent URI and
|
||||||
|
/// any future references to this resource should use one of the returned URIs.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Moved is a synonym for MovedPermanently.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
Moved = 301,
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Equivalent to status code 302.
|
||||||
|
/// Indicates that the requested resource is located temporarily under a different URI.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Found is a synonym for Redirect.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
Found = 302,
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Equivalent to status code 302.
|
||||||
|
/// Indicates that the requested resource is located temporarily under a different URI.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Redirect is a synonym for Found.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
Redirect = 302,
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Equivalent to status code 303.
|
||||||
|
/// Indicates that the response to the request can be found under a different URI and
|
||||||
|
/// should be retrieved using a GET method on that resource.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// SeeOther is a synonym for RedirectMethod.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
SeeOther = 303,
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Equivalent to status code 303.
|
||||||
|
/// Indicates that the response to the request can be found under a different URI and
|
||||||
|
/// should be retrieved using a GET method on that resource.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// RedirectMethod is a synonym for SeeOther.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
RedirectMethod = 303,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 304.
|
||||||
|
/// Indicates that the client has performed a conditional GET request and access is allowed,
|
||||||
|
/// but the document hasn't been modified.
|
||||||
|
/// </summary>
|
||||||
|
NotModified = 304,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 305.
|
||||||
|
/// Indicates that the requested resource must be accessed through the proxy given by
|
||||||
|
/// the Location field.
|
||||||
|
/// </summary>
|
||||||
|
UseProxy = 305,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 306.
|
||||||
|
/// This status code was used in a previous version of the specification, is no longer used,
|
||||||
|
/// and is reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
Unused = 306,
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Equivalent to status code 307.
|
||||||
|
/// Indicates that the requested resource is located temporarily under a different URI.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// TemporaryRedirect is a synonym for RedirectKeepVerb.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
TemporaryRedirect = 307,
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Equivalent to status code 307.
|
||||||
|
/// Indicates that the requested resource is located temporarily under a different URI.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// RedirectKeepVerb is a synonym for TemporaryRedirect.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
RedirectKeepVerb = 307,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 400.
|
||||||
|
/// Indicates that the client's request couldn't be understood by the server due to
|
||||||
|
/// malformed syntax.
|
||||||
|
/// </summary>
|
||||||
|
BadRequest = 400,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 401.
|
||||||
|
/// Indicates that the client's request requires user authentication.
|
||||||
|
/// </summary>
|
||||||
|
Unauthorized = 401,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 402.
|
||||||
|
/// This status code is reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
PaymentRequired = 402,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 403.
|
||||||
|
/// Indicates that the server understood the client's request but is refusing to fulfill it.
|
||||||
|
/// </summary>
|
||||||
|
Forbidden = 403,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 404.
|
||||||
|
/// Indicates that the server hasn't found anything matching the request URI.
|
||||||
|
/// </summary>
|
||||||
|
NotFound = 404,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 405.
|
||||||
|
/// Indicates that the method specified in the request line isn't allowed for the resource
|
||||||
|
/// identified by the request URI.
|
||||||
|
/// </summary>
|
||||||
|
MethodNotAllowed = 405,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 406.
|
||||||
|
/// Indicates that the server doesn't have the appropriate resource to respond to the Accept
|
||||||
|
/// headers in the client's request.
|
||||||
|
/// </summary>
|
||||||
|
NotAcceptable = 406,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 407.
|
||||||
|
/// Indicates that the client must first authenticate itself with the proxy.
|
||||||
|
/// </summary>
|
||||||
|
ProxyAuthenticationRequired = 407,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 408.
|
||||||
|
/// Indicates that the client didn't produce a request within the time that the server was
|
||||||
|
/// prepared to wait.
|
||||||
|
/// </summary>
|
||||||
|
RequestTimeout = 408,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 409.
|
||||||
|
/// Indicates that the client's request couldn't be completed due to a conflict on the server.
|
||||||
|
/// </summary>
|
||||||
|
Conflict = 409,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 410.
|
||||||
|
/// Indicates that the requested resource is no longer available at the server and
|
||||||
|
/// no forwarding address is known.
|
||||||
|
/// </summary>
|
||||||
|
Gone = 410,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 411.
|
||||||
|
/// Indicates that the server refuses to accept the client's request without a defined
|
||||||
|
/// Content-Length.
|
||||||
|
/// </summary>
|
||||||
|
LengthRequired = 411,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 412.
|
||||||
|
/// Indicates that the precondition given in one or more of the request headers evaluated to
|
||||||
|
/// false when it was tested on the server.
|
||||||
|
/// </summary>
|
||||||
|
PreconditionFailed = 412,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 413.
|
||||||
|
/// Indicates that the entity of the client's request is larger than the server is willing or
|
||||||
|
/// able to process.
|
||||||
|
/// </summary>
|
||||||
|
RequestEntityTooLarge = 413,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 414.
|
||||||
|
/// Indicates that the request URI is longer than the server is willing to interpret.
|
||||||
|
/// </summary>
|
||||||
|
RequestUriTooLong = 414,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 415.
|
||||||
|
/// Indicates that the entity of the client's request is in a format not supported by
|
||||||
|
/// the requested resource for the requested method.
|
||||||
|
/// </summary>
|
||||||
|
UnsupportedMediaType = 415,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 416.
|
||||||
|
/// Indicates that none of the range specifier values in a Range request header overlap
|
||||||
|
/// the current extent of the selected resource.
|
||||||
|
/// </summary>
|
||||||
|
RequestedRangeNotSatisfiable = 416,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 417.
|
||||||
|
/// Indicates that the expectation given in an Expect request header couldn't be met by
|
||||||
|
/// the server.
|
||||||
|
/// </summary>
|
||||||
|
ExpectationFailed = 417,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 500.
|
||||||
|
/// Indicates that the server encountered an unexpected condition which prevented it from
|
||||||
|
/// fulfilling the client's request.
|
||||||
|
/// </summary>
|
||||||
|
InternalServerError = 500,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 501.
|
||||||
|
/// Indicates that the server doesn't support the functionality required to fulfill the client's
|
||||||
|
/// request.
|
||||||
|
/// </summary>
|
||||||
|
NotImplemented = 501,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 502.
|
||||||
|
/// Indicates that a gateway or proxy server received an invalid response from the upstream
|
||||||
|
/// server.
|
||||||
|
/// </summary>
|
||||||
|
BadGateway = 502,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 503.
|
||||||
|
/// Indicates that the server is currently unable to handle the client's request due to
|
||||||
|
/// a temporary overloading or maintenance of the server.
|
||||||
|
/// </summary>
|
||||||
|
ServiceUnavailable = 503,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 504.
|
||||||
|
/// Indicates that a gateway or proxy server didn't receive a timely response from the upstream
|
||||||
|
/// server or some other auxiliary server.
|
||||||
|
/// </summary>
|
||||||
|
GatewayTimeout = 504,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to status code 505.
|
||||||
|
/// Indicates that the server doesn't support the HTTP version used in the client's request.
|
||||||
|
/// </summary>
|
||||||
|
HttpVersionNotSupported = 505,
|
||||||
|
}
|
||||||
|
}
|
77
SocketHttpListener.Portable/Net/HttpStreamAsyncResult.cs
Normal file
77
SocketHttpListener.Portable/Net/HttpStreamAsyncResult.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
class HttpStreamAsyncResult : IAsyncResult
|
||||||
|
{
|
||||||
|
object locker = new object();
|
||||||
|
ManualResetEvent handle;
|
||||||
|
bool completed;
|
||||||
|
|
||||||
|
internal byte[] Buffer;
|
||||||
|
internal int Offset;
|
||||||
|
internal int Count;
|
||||||
|
internal AsyncCallback Callback;
|
||||||
|
internal object State;
|
||||||
|
internal int SynchRead;
|
||||||
|
internal Exception Error;
|
||||||
|
|
||||||
|
public void Complete(Exception e)
|
||||||
|
{
|
||||||
|
Error = e;
|
||||||
|
Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Complete()
|
||||||
|
{
|
||||||
|
lock (locker)
|
||||||
|
{
|
||||||
|
if (completed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
completed = true;
|
||||||
|
if (handle != null)
|
||||||
|
handle.Set();
|
||||||
|
|
||||||
|
if (Callback != null)
|
||||||
|
Callback.BeginInvoke(this, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object AsyncState
|
||||||
|
{
|
||||||
|
get { return State; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public WaitHandle AsyncWaitHandle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (locker)
|
||||||
|
{
|
||||||
|
if (handle == null)
|
||||||
|
handle = new ManualResetEvent(completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CompletedSynchronously
|
||||||
|
{
|
||||||
|
get { return (SynchRead == Count); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCompleted
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (locker)
|
||||||
|
{
|
||||||
|
return completed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
SocketHttpListener.Portable/Net/HttpVersion.cs
Normal file
16
SocketHttpListener.Portable/Net/HttpVersion.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
// <remarks>
|
||||||
|
// </remarks>
|
||||||
|
public class HttpVersion
|
||||||
|
{
|
||||||
|
|
||||||
|
public static readonly Version Version10 = new Version(1, 0);
|
||||||
|
public static readonly Version Version11 = new Version(1, 1);
|
||||||
|
|
||||||
|
// pretty useless..
|
||||||
|
public HttpVersion() { }
|
||||||
|
}
|
||||||
|
}
|
148
SocketHttpListener.Portable/Net/ListenerPrefix.cs
Normal file
148
SocketHttpListener.Portable/Net/ListenerPrefix.cs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
sealed class ListenerPrefix
|
||||||
|
{
|
||||||
|
string original;
|
||||||
|
string host;
|
||||||
|
ushort port;
|
||||||
|
string path;
|
||||||
|
bool secure;
|
||||||
|
IpAddressInfo[] addresses;
|
||||||
|
public HttpListener Listener;
|
||||||
|
|
||||||
|
public ListenerPrefix(string prefix)
|
||||||
|
{
|
||||||
|
this.original = prefix;
|
||||||
|
Parse(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IpAddressInfo[] Addresses
|
||||||
|
{
|
||||||
|
get { return addresses; }
|
||||||
|
set { addresses = value; }
|
||||||
|
}
|
||||||
|
public bool Secure
|
||||||
|
{
|
||||||
|
get { return secure; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Host
|
||||||
|
{
|
||||||
|
get { return host; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Port
|
||||||
|
{
|
||||||
|
get { return (int)port; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path
|
||||||
|
{
|
||||||
|
get { return path; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection.
|
||||||
|
public override bool Equals(object o)
|
||||||
|
{
|
||||||
|
ListenerPrefix other = o as ListenerPrefix;
|
||||||
|
if (other == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return (original == other.original);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return original.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parse(string uri)
|
||||||
|
{
|
||||||
|
ushort default_port = 80;
|
||||||
|
if (uri.StartsWith("https://"))
|
||||||
|
{
|
||||||
|
default_port = 443;
|
||||||
|
secure = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = uri.Length;
|
||||||
|
int start_host = uri.IndexOf(':') + 3;
|
||||||
|
if (start_host >= length)
|
||||||
|
throw new ArgumentException("No host specified.");
|
||||||
|
|
||||||
|
int colon = uri.IndexOf(':', start_host, length - start_host);
|
||||||
|
int root;
|
||||||
|
if (colon > 0)
|
||||||
|
{
|
||||||
|
host = uri.Substring(start_host, colon - start_host);
|
||||||
|
root = uri.IndexOf('/', colon, length - colon);
|
||||||
|
port = (ushort)Int32.Parse(uri.Substring(colon + 1, root - colon - 1));
|
||||||
|
path = uri.Substring(root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
root = uri.IndexOf('/', start_host, length - start_host);
|
||||||
|
host = uri.Substring(start_host, root - start_host);
|
||||||
|
port = default_port;
|
||||||
|
path = uri.Substring(root);
|
||||||
|
}
|
||||||
|
if (path.Length != 1)
|
||||||
|
path = path.Substring(0, path.Length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CheckUri(string uri)
|
||||||
|
{
|
||||||
|
if (uri == null)
|
||||||
|
throw new ArgumentNullException("uriPrefix");
|
||||||
|
|
||||||
|
if (!uri.StartsWith("http://") && !uri.StartsWith("https://"))
|
||||||
|
throw new ArgumentException("Only 'http' and 'https' schemes are supported.");
|
||||||
|
|
||||||
|
int length = uri.Length;
|
||||||
|
int start_host = uri.IndexOf(':') + 3;
|
||||||
|
if (start_host >= length)
|
||||||
|
throw new ArgumentException("No host specified.");
|
||||||
|
|
||||||
|
int colon = uri.IndexOf(':', start_host, length - start_host);
|
||||||
|
if (start_host == colon)
|
||||||
|
throw new ArgumentException("No host specified.");
|
||||||
|
|
||||||
|
int root;
|
||||||
|
if (colon > 0)
|
||||||
|
{
|
||||||
|
root = uri.IndexOf('/', colon, length - colon);
|
||||||
|
if (root == -1)
|
||||||
|
throw new ArgumentException("No path specified.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int p = Int32.Parse(uri.Substring(colon + 1, root - colon - 1));
|
||||||
|
if (p <= 0 || p >= 65536)
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid port.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
root = uri.IndexOf('/', start_host, length - start_host);
|
||||||
|
if (root == -1)
|
||||||
|
throw new ArgumentException("No path specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri[uri.Length - 1] != '/')
|
||||||
|
throw new ArgumentException("The prefix must end with '/'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
231
SocketHttpListener.Portable/Net/RequestStream.cs
Normal file
231
SocketHttpListener.Portable/Net/RequestStream.cs
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
class RequestStream : Stream
|
||||||
|
{
|
||||||
|
byte[] buffer;
|
||||||
|
int offset;
|
||||||
|
int length;
|
||||||
|
long remaining_body;
|
||||||
|
bool disposed;
|
||||||
|
Stream stream;
|
||||||
|
|
||||||
|
internal RequestStream(Stream stream, byte[] buffer, int offset, int length)
|
||||||
|
: this(stream, buffer, offset, length, -1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RequestStream(Stream stream, byte[] buffer, int offset, int length, long contentlength)
|
||||||
|
{
|
||||||
|
this.stream = stream;
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.offset = offset;
|
||||||
|
this.length = length;
|
||||||
|
this.remaining_body = contentlength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get { throw new NotSupportedException(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get { throw new NotSupportedException(); }
|
||||||
|
set { throw new NotSupportedException(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Returns 0 if we can keep reading from the base stream,
|
||||||
|
// > 0 if we read something from the buffer.
|
||||||
|
// -1 if we had a content length set and we finished reading that many bytes.
|
||||||
|
int FillFromBuffer(byte[] buffer, int off, int count)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
throw new ArgumentNullException("buffer");
|
||||||
|
if (off < 0)
|
||||||
|
throw new ArgumentOutOfRangeException("offset", "< 0");
|
||||||
|
if (count < 0)
|
||||||
|
throw new ArgumentOutOfRangeException("count", "< 0");
|
||||||
|
int len = buffer.Length;
|
||||||
|
if (off > len)
|
||||||
|
throw new ArgumentException("destination offset is beyond array size");
|
||||||
|
if (off > len - count)
|
||||||
|
throw new ArgumentException("Reading would overrun buffer");
|
||||||
|
|
||||||
|
if (this.remaining_body == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (this.length == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int size = Math.Min(this.length, count);
|
||||||
|
if (this.remaining_body > 0)
|
||||||
|
size = (int)Math.Min(size, this.remaining_body);
|
||||||
|
|
||||||
|
if (this.offset > this.buffer.Length - size)
|
||||||
|
{
|
||||||
|
size = Math.Min(size, this.buffer.Length - this.offset);
|
||||||
|
}
|
||||||
|
if (size == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
Buffer.BlockCopy(this.buffer, this.offset, buffer, off, size);
|
||||||
|
this.offset += size;
|
||||||
|
this.length -= size;
|
||||||
|
if (this.remaining_body > 0)
|
||||||
|
remaining_body -= size;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read([In, Out] byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(typeof(RequestStream).ToString());
|
||||||
|
|
||||||
|
// Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0
|
||||||
|
int nread = FillFromBuffer(buffer, offset, count);
|
||||||
|
if (nread == -1)
|
||||||
|
{ // No more bytes available (Content-Length)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (nread > 0)
|
||||||
|
{
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
nread = stream.Read(buffer, offset, count);
|
||||||
|
if (nread > 0 && remaining_body > 0)
|
||||||
|
remaining_body -= nread;
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(typeof(RequestStream).ToString());
|
||||||
|
|
||||||
|
int nread = FillFromBuffer(buffer, offset, count);
|
||||||
|
if (nread > 0 || nread == -1)
|
||||||
|
{
|
||||||
|
return Math.Max(0, nread);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid reading past the end of the request to allow
|
||||||
|
// for HTTP pipelining
|
||||||
|
if (remaining_body >= 0 && count > remaining_body)
|
||||||
|
count = (int)Math.Min(Int32.MaxValue, remaining_body);
|
||||||
|
|
||||||
|
nread = await stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (remaining_body > 0 && nread > 0)
|
||||||
|
remaining_body -= nread;
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
//public override IAsyncResult BeginRead(byte[] buffer, int offset, int count,
|
||||||
|
// AsyncCallback cback, object state)
|
||||||
|
//{
|
||||||
|
// if (disposed)
|
||||||
|
// throw new ObjectDisposedException(typeof(RequestStream).ToString());
|
||||||
|
|
||||||
|
// int nread = FillFromBuffer(buffer, offset, count);
|
||||||
|
// if (nread > 0 || nread == -1)
|
||||||
|
// {
|
||||||
|
// HttpStreamAsyncResult ares = new HttpStreamAsyncResult();
|
||||||
|
// ares.Buffer = buffer;
|
||||||
|
// ares.Offset = offset;
|
||||||
|
// ares.Count = count;
|
||||||
|
// ares.Callback = cback;
|
||||||
|
// ares.State = state;
|
||||||
|
// ares.SynchRead = Math.Max(0, nread);
|
||||||
|
// ares.Complete();
|
||||||
|
// return ares;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Avoid reading past the end of the request to allow
|
||||||
|
// // for HTTP pipelining
|
||||||
|
// if (remaining_body >= 0 && count > remaining_body)
|
||||||
|
// count = (int)Math.Min(Int32.MaxValue, remaining_body);
|
||||||
|
// return stream.BeginRead(buffer, offset, count, cback, state);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public override int EndRead(IAsyncResult ares)
|
||||||
|
//{
|
||||||
|
// if (disposed)
|
||||||
|
// throw new ObjectDisposedException(typeof(RequestStream).ToString());
|
||||||
|
|
||||||
|
// if (ares == null)
|
||||||
|
// throw new ArgumentNullException("async_result");
|
||||||
|
|
||||||
|
// if (ares is HttpStreamAsyncResult)
|
||||||
|
// {
|
||||||
|
// HttpStreamAsyncResult r = (HttpStreamAsyncResult)ares;
|
||||||
|
// if (!ares.IsCompleted)
|
||||||
|
// ares.AsyncWaitHandle.WaitOne();
|
||||||
|
// return r.SynchRead;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Close on exception?
|
||||||
|
// int nread = stream.EndRead(ares);
|
||||||
|
// if (remaining_body > 0 && nread > 0)
|
||||||
|
// remaining_body -= nread;
|
||||||
|
// return nread;
|
||||||
|
//}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
//public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count,
|
||||||
|
// AsyncCallback cback, object state)
|
||||||
|
//{
|
||||||
|
// throw new NotSupportedException();
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public override void EndWrite(IAsyncResult async_result)
|
||||||
|
//{
|
||||||
|
// throw new NotSupportedException();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
316
SocketHttpListener.Portable/Net/ResponseStream.cs
Normal file
316
SocketHttpListener.Portable/Net/ResponseStream.cs
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Text;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
// FIXME: Does this buffer the response until Close?
|
||||||
|
// Update: we send a single packet for the first non-chunked Write
|
||||||
|
// What happens when we set content-length to X and write X-1 bytes then close?
|
||||||
|
// what if we don't set content-length at all?
|
||||||
|
class ResponseStream : Stream
|
||||||
|
{
|
||||||
|
HttpListenerResponse response;
|
||||||
|
bool ignore_errors;
|
||||||
|
bool disposed;
|
||||||
|
bool trailer_sent;
|
||||||
|
Stream stream;
|
||||||
|
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||||
|
private readonly ITextEncoding _textEncoding;
|
||||||
|
|
||||||
|
internal ResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
|
||||||
|
{
|
||||||
|
this.response = response;
|
||||||
|
this.ignore_errors = ignore_errors;
|
||||||
|
_memoryStreamFactory = memoryStreamFactory;
|
||||||
|
_textEncoding = textEncoding;
|
||||||
|
this.stream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get { throw new NotSupportedException(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get { throw new NotSupportedException(); }
|
||||||
|
set { throw new NotSupportedException(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposed == false)
|
||||||
|
{
|
||||||
|
disposed = true;
|
||||||
|
byte[] bytes = null;
|
||||||
|
MemoryStream ms = GetHeaders(true);
|
||||||
|
bool chunked = response.SendChunked;
|
||||||
|
if (stream.CanWrite)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ms != null)
|
||||||
|
{
|
||||||
|
long start = ms.Position;
|
||||||
|
if (chunked && !trailer_sent)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(0, true);
|
||||||
|
ms.Position = ms.Length;
|
||||||
|
ms.Write(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
byte[] msBuffer;
|
||||||
|
_memoryStreamFactory.TryGetBuffer(ms, out msBuffer);
|
||||||
|
InternalWrite(msBuffer, (int)start, (int)(ms.Length - start));
|
||||||
|
trailer_sent = true;
|
||||||
|
}
|
||||||
|
else if (chunked && !trailer_sent)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(0, true);
|
||||||
|
InternalWrite(bytes, 0, bytes.Length);
|
||||||
|
trailer_sent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
// Ignore error due to connection reset by peer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryStream GetHeaders(bool closing)
|
||||||
|
{
|
||||||
|
// SendHeaders works on shared headers
|
||||||
|
lock (response.headers_lock)
|
||||||
|
{
|
||||||
|
if (response.HeadersSent)
|
||||||
|
return null;
|
||||||
|
MemoryStream ms = _memoryStreamFactory.CreateNew();
|
||||||
|
response.SendHeaders(closing, ms);
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] crlf = new byte[] { 13, 10 };
|
||||||
|
byte[] GetChunkSizeBytes(int size, bool final)
|
||||||
|
{
|
||||||
|
string str = String.Format("{0:x}\r\n{1}", size, final ? "\r\n" : "");
|
||||||
|
return _textEncoding.GetASCIIEncoding().GetBytes(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InternalWrite(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (ignore_errors)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stream.Write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream.Write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
byte[] bytes = null;
|
||||||
|
MemoryStream ms = GetHeaders(false);
|
||||||
|
bool chunked = response.SendChunked;
|
||||||
|
if (ms != null)
|
||||||
|
{
|
||||||
|
long start = ms.Position; // After the possible preamble for the encoding
|
||||||
|
ms.Position = ms.Length;
|
||||||
|
if (chunked)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(count, false);
|
||||||
|
ms.Write(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int new_count = Math.Min(count, 16384 - (int)ms.Position + (int)start);
|
||||||
|
ms.Write(buffer, offset, new_count);
|
||||||
|
count -= new_count;
|
||||||
|
offset += new_count;
|
||||||
|
byte[] msBuffer;
|
||||||
|
_memoryStreamFactory.TryGetBuffer(ms, out msBuffer);
|
||||||
|
InternalWrite(msBuffer, (int)start, (int)(ms.Length - start));
|
||||||
|
ms.SetLength(0);
|
||||||
|
ms.Capacity = 0; // 'dispose' the buffer in ms.
|
||||||
|
}
|
||||||
|
else if (chunked)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(count, false);
|
||||||
|
InternalWrite(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
|
InternalWrite(buffer, offset, count);
|
||||||
|
if (chunked)
|
||||||
|
InternalWrite(crlf, 0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
byte[] bytes = null;
|
||||||
|
MemoryStream ms = GetHeaders(false);
|
||||||
|
bool chunked = response.SendChunked;
|
||||||
|
if (ms != null)
|
||||||
|
{
|
||||||
|
long start = ms.Position;
|
||||||
|
ms.Position = ms.Length;
|
||||||
|
if (chunked)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(count, false);
|
||||||
|
ms.Write(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
ms.Write(buffer, offset, count);
|
||||||
|
byte[] msBuffer;
|
||||||
|
_memoryStreamFactory.TryGetBuffer(ms, out msBuffer);
|
||||||
|
buffer = msBuffer;
|
||||||
|
offset = (int)start;
|
||||||
|
count = (int)(ms.Position - start);
|
||||||
|
}
|
||||||
|
else if (chunked)
|
||||||
|
{
|
||||||
|
bytes = GetChunkSizeBytes(count, false);
|
||||||
|
InternalWrite(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.SendChunked)
|
||||||
|
stream.Write(crlf, 0, 2);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (!ignore_errors)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count,
|
||||||
|
// AsyncCallback cback, object state)
|
||||||
|
//{
|
||||||
|
// if (disposed)
|
||||||
|
// throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
// byte[] bytes = null;
|
||||||
|
// MemoryStream ms = GetHeaders(false);
|
||||||
|
// bool chunked = response.SendChunked;
|
||||||
|
// if (ms != null)
|
||||||
|
// {
|
||||||
|
// long start = ms.Position;
|
||||||
|
// ms.Position = ms.Length;
|
||||||
|
// if (chunked)
|
||||||
|
// {
|
||||||
|
// bytes = GetChunkSizeBytes(count, false);
|
||||||
|
// ms.Write(bytes, 0, bytes.Length);
|
||||||
|
// }
|
||||||
|
// ms.Write(buffer, offset, count);
|
||||||
|
// buffer = ms.ToArray();
|
||||||
|
// offset = (int)start;
|
||||||
|
// count = (int)(ms.Position - start);
|
||||||
|
// }
|
||||||
|
// else if (chunked)
|
||||||
|
// {
|
||||||
|
// bytes = GetChunkSizeBytes(count, false);
|
||||||
|
// InternalWrite(bytes, 0, bytes.Length);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return stream.BeginWrite(buffer, offset, count, cback, state);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public override void EndWrite(IAsyncResult ares)
|
||||||
|
//{
|
||||||
|
// if (disposed)
|
||||||
|
// throw new ObjectDisposedException(GetType().ToString());
|
||||||
|
|
||||||
|
// if (ignore_errors)
|
||||||
|
// {
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// stream.EndWrite(ares);
|
||||||
|
// if (response.SendChunked)
|
||||||
|
// stream.Write(crlf, 0, 2);
|
||||||
|
// }
|
||||||
|
// catch { }
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// stream.EndWrite(ares);
|
||||||
|
// if (response.SendChunked)
|
||||||
|
// stream.Write(crlf, 0, 2);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
public override int Read([In, Out] byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
//public override IAsyncResult BeginRead(byte[] buffer, int offset, int count,
|
||||||
|
// AsyncCallback cback, object state)
|
||||||
|
//{
|
||||||
|
// throw new NotSupportedException();
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public override int EndRead(IAsyncResult ares)
|
||||||
|
//{
|
||||||
|
// throw new NotSupportedException();
|
||||||
|
//}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
391
SocketHttpListener.Portable/Net/WebHeaderCollection.cs
Normal file
391
SocketHttpListener.Portable/Net/WebHeaderCollection.cs
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Net;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Text;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net
|
||||||
|
{
|
||||||
|
[ComVisible(true)]
|
||||||
|
public class WebHeaderCollection : QueryParamCollection
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
internal enum HeaderInfo
|
||||||
|
{
|
||||||
|
Request = 1,
|
||||||
|
Response = 1 << 1,
|
||||||
|
MultiValue = 1 << 10
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly bool[] allowed_chars = {
|
||||||
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false,
|
||||||
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false,
|
||||||
|
false, false, false, false, false, true, false, true, true, true, true, false, false, false, true,
|
||||||
|
true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false,
|
||||||
|
false, false, false, false, false, false, true, true, true, true, true, true, true, true, true,
|
||||||
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
||||||
|
false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
||||||
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
||||||
|
false, true, false
|
||||||
|
};
|
||||||
|
|
||||||
|
static readonly Dictionary<string, HeaderInfo> headers;
|
||||||
|
HeaderInfo? headerRestriction;
|
||||||
|
HeaderInfo? headerConsistency;
|
||||||
|
|
||||||
|
static WebHeaderCollection()
|
||||||
|
{
|
||||||
|
headers = new Dictionary<string, HeaderInfo>(StringComparer.OrdinalIgnoreCase) {
|
||||||
|
{ "Allow", HeaderInfo.MultiValue },
|
||||||
|
{ "Accept", HeaderInfo.Request | HeaderInfo.MultiValue },
|
||||||
|
{ "Accept-Charset", HeaderInfo.MultiValue },
|
||||||
|
{ "Accept-Encoding", HeaderInfo.MultiValue },
|
||||||
|
{ "Accept-Language", HeaderInfo.MultiValue },
|
||||||
|
{ "Accept-Ranges", HeaderInfo.MultiValue },
|
||||||
|
{ "Age", HeaderInfo.Response },
|
||||||
|
{ "Authorization", HeaderInfo.MultiValue },
|
||||||
|
{ "Cache-Control", HeaderInfo.MultiValue },
|
||||||
|
{ "Cookie", HeaderInfo.MultiValue },
|
||||||
|
{ "Connection", HeaderInfo.Request | HeaderInfo.MultiValue },
|
||||||
|
{ "Content-Encoding", HeaderInfo.MultiValue },
|
||||||
|
{ "Content-Length", HeaderInfo.Request | HeaderInfo.Response },
|
||||||
|
{ "Content-Type", HeaderInfo.Request },
|
||||||
|
{ "Content-Language", HeaderInfo.MultiValue },
|
||||||
|
{ "Date", HeaderInfo.Request },
|
||||||
|
{ "Expect", HeaderInfo.Request | HeaderInfo.MultiValue},
|
||||||
|
{ "Host", HeaderInfo.Request },
|
||||||
|
{ "If-Match", HeaderInfo.MultiValue },
|
||||||
|
{ "If-Modified-Since", HeaderInfo.Request },
|
||||||
|
{ "If-None-Match", HeaderInfo.MultiValue },
|
||||||
|
{ "Keep-Alive", HeaderInfo.Response },
|
||||||
|
{ "Pragma", HeaderInfo.MultiValue },
|
||||||
|
{ "Proxy-Authenticate", HeaderInfo.MultiValue },
|
||||||
|
{ "Proxy-Authorization", HeaderInfo.MultiValue },
|
||||||
|
{ "Proxy-Connection", HeaderInfo.Request | HeaderInfo.MultiValue },
|
||||||
|
{ "Range", HeaderInfo.Request | HeaderInfo.MultiValue },
|
||||||
|
{ "Referer", HeaderInfo.Request },
|
||||||
|
{ "Set-Cookie", HeaderInfo.MultiValue },
|
||||||
|
{ "Set-Cookie2", HeaderInfo.MultiValue },
|
||||||
|
{ "Server", HeaderInfo.Response },
|
||||||
|
{ "TE", HeaderInfo.MultiValue },
|
||||||
|
{ "Trailer", HeaderInfo.MultiValue },
|
||||||
|
{ "Transfer-Encoding", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo.MultiValue },
|
||||||
|
{ "Translate", HeaderInfo.Request | HeaderInfo.Response },
|
||||||
|
{ "Upgrade", HeaderInfo.MultiValue },
|
||||||
|
{ "User-Agent", HeaderInfo.Request },
|
||||||
|
{ "Vary", HeaderInfo.MultiValue },
|
||||||
|
{ "Via", HeaderInfo.MultiValue },
|
||||||
|
{ "Warning", HeaderInfo.MultiValue },
|
||||||
|
{ "WWW-Authenticate", HeaderInfo.Response | HeaderInfo. MultiValue },
|
||||||
|
{ "SecWebSocketAccept", HeaderInfo.Response },
|
||||||
|
{ "SecWebSocketExtensions", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue },
|
||||||
|
{ "SecWebSocketKey", HeaderInfo.Request },
|
||||||
|
{ "Sec-WebSocket-Protocol", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue },
|
||||||
|
{ "SecWebSocketVersion", HeaderInfo.Response | HeaderInfo. MultiValue }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
|
||||||
|
public void Add(string header)
|
||||||
|
{
|
||||||
|
if (header == null)
|
||||||
|
throw new ArgumentNullException("header");
|
||||||
|
int pos = header.IndexOf(':');
|
||||||
|
if (pos == -1)
|
||||||
|
throw new ArgumentException("no colon found", "header");
|
||||||
|
|
||||||
|
this.Add(header.Substring(0, pos), header.Substring(pos + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Add(string name, string value)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
|
||||||
|
ThrowIfRestricted(name);
|
||||||
|
this.AddWithoutValidate(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AddWithoutValidate(string headerName, string headerValue)
|
||||||
|
{
|
||||||
|
if (!IsHeaderName(headerName))
|
||||||
|
throw new ArgumentException("invalid header name: " + headerName, "headerName");
|
||||||
|
if (headerValue == null)
|
||||||
|
headerValue = String.Empty;
|
||||||
|
else
|
||||||
|
headerValue = headerValue.Trim();
|
||||||
|
if (!IsHeaderValue(headerValue))
|
||||||
|
throw new ArgumentException("invalid header value: " + headerValue, "headerValue");
|
||||||
|
|
||||||
|
AddValue(headerName, headerValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddValue(string headerName, string headerValue)
|
||||||
|
{
|
||||||
|
base.Add(headerName, headerValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string[] GetValues_internal(string header, bool split)
|
||||||
|
{
|
||||||
|
if (header == null)
|
||||||
|
throw new ArgumentNullException("header");
|
||||||
|
|
||||||
|
string[] values = base.GetValues(header);
|
||||||
|
if (values == null || values.Length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (split && IsMultiValue(header))
|
||||||
|
{
|
||||||
|
List<string> separated = null;
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
if (value.IndexOf(',') < 0)
|
||||||
|
{
|
||||||
|
if (separated != null)
|
||||||
|
separated.Add(value);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (separated == null)
|
||||||
|
{
|
||||||
|
separated = new List<string>(values.Length + 1);
|
||||||
|
foreach (var v in values)
|
||||||
|
{
|
||||||
|
if (v == value)
|
||||||
|
break;
|
||||||
|
|
||||||
|
separated.Add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var slices = value.Split(',');
|
||||||
|
var slices_length = slices.Length;
|
||||||
|
if (value[value.Length - 1] == ',')
|
||||||
|
--slices_length;
|
||||||
|
|
||||||
|
for (int i = 0; i < slices_length; ++i)
|
||||||
|
{
|
||||||
|
separated.Add(slices[i].Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (separated != null)
|
||||||
|
return separated.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string[] GetValues(string header)
|
||||||
|
{
|
||||||
|
return GetValues_internal(header, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string[] GetValues(int index)
|
||||||
|
{
|
||||||
|
string[] values = base.GetValues(index);
|
||||||
|
|
||||||
|
if (values == null || values.Length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsRestricted(string headerName)
|
||||||
|
{
|
||||||
|
return IsRestricted(headerName, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsRestricted(string headerName, bool response)
|
||||||
|
{
|
||||||
|
if (headerName == null)
|
||||||
|
throw new ArgumentNullException("headerName");
|
||||||
|
|
||||||
|
if (headerName.Length == 0)
|
||||||
|
throw new ArgumentException("empty string", "headerName");
|
||||||
|
|
||||||
|
if (!IsHeaderName(headerName))
|
||||||
|
throw new ArgumentException("Invalid character in header");
|
||||||
|
|
||||||
|
HeaderInfo info;
|
||||||
|
if (!headers.TryGetValue(headerName, out info))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var flag = response ? HeaderInfo.Response : HeaderInfo.Request;
|
||||||
|
return (info & flag) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Set(string name, string value)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
if (!IsHeaderName(name))
|
||||||
|
throw new ArgumentException("invalid header name");
|
||||||
|
if (value == null)
|
||||||
|
value = String.Empty;
|
||||||
|
else
|
||||||
|
value = value.Trim();
|
||||||
|
if (!IsHeaderValue(value))
|
||||||
|
throw new ArgumentException("invalid header value");
|
||||||
|
|
||||||
|
ThrowIfRestricted(name);
|
||||||
|
base.Set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string ToStringMultiValue()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
int count = base.Count;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
string key = GetKey(i);
|
||||||
|
if (IsMultiValue(key))
|
||||||
|
{
|
||||||
|
foreach (string v in GetValues(i))
|
||||||
|
{
|
||||||
|
sb.Append(key)
|
||||||
|
.Append(": ")
|
||||||
|
.Append(v)
|
||||||
|
.Append("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(key)
|
||||||
|
.Append(": ")
|
||||||
|
.Append(Get(i))
|
||||||
|
.Append("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.Append("\r\n").ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
int count = base.Count;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
sb.Append(GetKey(i))
|
||||||
|
.Append(": ")
|
||||||
|
.Append(Get(i))
|
||||||
|
.Append("\r\n");
|
||||||
|
|
||||||
|
return sb.Append("\r\n").ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Internal Methods
|
||||||
|
|
||||||
|
// With this we don't check for invalid characters in header. See bug #55994.
|
||||||
|
internal void SetInternal(string header)
|
||||||
|
{
|
||||||
|
int pos = header.IndexOf(':');
|
||||||
|
if (pos == -1)
|
||||||
|
throw new ArgumentException("no colon found", "header");
|
||||||
|
|
||||||
|
SetInternal(header.Substring(0, pos), header.Substring(pos + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetInternal(string name, string value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
value = String.Empty;
|
||||||
|
else
|
||||||
|
value = value.Trim();
|
||||||
|
if (!IsHeaderValue(value))
|
||||||
|
throw new ArgumentException("invalid header value");
|
||||||
|
|
||||||
|
if (IsMultiValue(name))
|
||||||
|
{
|
||||||
|
base.Add(name, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base.Remove(name);
|
||||||
|
base.Set(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private Methods
|
||||||
|
|
||||||
|
public override int Remove(string name)
|
||||||
|
{
|
||||||
|
ThrowIfRestricted(name);
|
||||||
|
return base.Remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ThrowIfRestricted(string headerName)
|
||||||
|
{
|
||||||
|
if (!headerRestriction.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
HeaderInfo info;
|
||||||
|
if (!headers.TryGetValue(headerName, out info))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((info & headerRestriction.Value) != 0)
|
||||||
|
throw new ArgumentException("This header must be modified with the appropriate property.");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsMultiValue(string headerName)
|
||||||
|
{
|
||||||
|
if (headerName == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
HeaderInfo info;
|
||||||
|
return headers.TryGetValue(headerName, out info) && (info & HeaderInfo.MultiValue) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsHeaderValue(string value)
|
||||||
|
{
|
||||||
|
// TEXT any 8 bit value except CTL's (0-31 and 127)
|
||||||
|
// but including \r\n space and \t
|
||||||
|
// after a newline at least one space or \t must follow
|
||||||
|
// certain header fields allow comments ()
|
||||||
|
|
||||||
|
int len = value.Length;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
char c = value[i];
|
||||||
|
if (c == 127)
|
||||||
|
return false;
|
||||||
|
if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
|
||||||
|
return false;
|
||||||
|
if (c == '\n' && ++i < len)
|
||||||
|
{
|
||||||
|
c = value[i];
|
||||||
|
if (c != ' ' && c != '\t')
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsHeaderName(string name)
|
||||||
|
{
|
||||||
|
if (name == null || name.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int len = name.Length;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
char c = name[i];
|
||||||
|
if (c > 126 || !allowed_chars[c])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,347 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net.WebSockets
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the properties used to access the information in a WebSocket connection request
|
||||||
|
/// received by the <see cref="HttpListener"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// </remarks>
|
||||||
|
public class HttpListenerWebSocketContext : WebSocketContext
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private HttpListenerContext _context;
|
||||||
|
private WebSocket _websocket;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Constructors
|
||||||
|
|
||||||
|
internal HttpListenerWebSocketContext(
|
||||||
|
HttpListenerContext context, string protocol, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_websocket = new WebSocket(this, protocol, cryptoProvider, memoryStreamFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Properties
|
||||||
|
|
||||||
|
internal Stream Stream
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Connection.Stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the HTTP cookies included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="System.Net.CookieCollection"/> that contains the cookies.
|
||||||
|
/// </value>
|
||||||
|
public override CookieCollection CookieCollection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.Cookies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the HTTP headers included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="QueryParamCollection"/> that contains the headers.
|
||||||
|
/// </value>
|
||||||
|
public override QueryParamCollection Headers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.Headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the Host header included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that represents the value of the Host header.
|
||||||
|
/// </value>
|
||||||
|
public override string Host
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.Headers["Host"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the client is authenticated.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public override bool IsAuthenticated
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.IsAuthenticated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the client connected from the local computer.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if the client connected from the local computer; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public override bool IsLocal
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.IsLocal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the WebSocket connection is secured.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if the connection is secured; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public override bool IsSecureConnection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Connection.IsSecure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the request is a WebSocket connection request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public override bool IsWebSocketRequest
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.IsWebSocketRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the Origin header included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that represents the value of the Origin header.
|
||||||
|
/// </value>
|
||||||
|
public override string Origin
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.Headers["Origin"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the query string included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="QueryParamCollection"/> that contains the query string parameters.
|
||||||
|
/// </value>
|
||||||
|
public override QueryParamCollection QueryString
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.QueryString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the URI requested by the client.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="Uri"/> that represents the requested URI.
|
||||||
|
/// </value>
|
||||||
|
public override Uri RequestUri
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.Url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the Sec-WebSocket-Key header included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property provides a part of the information used by the server to prove that it
|
||||||
|
/// received a valid WebSocket connection request.
|
||||||
|
/// </remarks>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Key header.
|
||||||
|
/// </value>
|
||||||
|
public override string SecWebSocketKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.Headers["Sec-WebSocket-Key"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the values of the Sec-WebSocket-Protocol header included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property represents the subprotocols requested by the client.
|
||||||
|
/// </remarks>
|
||||||
|
/// <value>
|
||||||
|
/// An <see cref="T:System.Collections.Generic.IEnumerable{string}"/> instance that provides
|
||||||
|
/// an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol
|
||||||
|
/// header.
|
||||||
|
/// </value>
|
||||||
|
public override IEnumerable<string> SecWebSocketProtocols
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var protocols = _context.Request.Headers["Sec-WebSocket-Protocol"];
|
||||||
|
if (protocols != null)
|
||||||
|
foreach (var protocol in protocols.Split(','))
|
||||||
|
yield return protocol.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the Sec-WebSocket-Version header included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property represents the WebSocket protocol version.
|
||||||
|
/// </remarks>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Version header.
|
||||||
|
/// </value>
|
||||||
|
public override string SecWebSocketVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Request.Headers["Sec-WebSocket-Version"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the server endpoint as an IP address and a port number.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// </value>
|
||||||
|
public override IpEndPointInfo ServerEndPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Connection.LocalEndPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the client information (identity, authentication, and security roles).
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="IPrincipal"/> that represents the client information.
|
||||||
|
/// </value>
|
||||||
|
public override IPrincipal User
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.User;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the client endpoint as an IP address and a port number.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// </value>
|
||||||
|
public override IpEndPointInfo UserEndPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _context.Connection.RemoteEndPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="SocketHttpListener.WebSocket"/> instance used for two-way communication
|
||||||
|
/// between client and server.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="SocketHttpListener.WebSocket"/>.
|
||||||
|
/// </value>
|
||||||
|
public override WebSocket WebSocket
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _websocket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Methods
|
||||||
|
|
||||||
|
internal void Close()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_context.Connection.Close(true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// catch errors sending the closing handshake
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Close(HttpStatusCode code)
|
||||||
|
{
|
||||||
|
_context.Response.Close(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a <see cref="string"/> that represents the current
|
||||||
|
/// <see cref="HttpListenerWebSocketContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="string"/> that represents the current
|
||||||
|
/// <see cref="HttpListenerWebSocketContext"/>.
|
||||||
|
/// </returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return _context.Request.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
183
SocketHttpListener.Portable/Net/WebSockets/WebSocketContext.cs
Normal file
183
SocketHttpListener.Portable/Net/WebSockets/WebSocketContext.cs
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Net.WebSockets
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Exposes the properties used to access the information in a WebSocket connection request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The WebSocketContext class is an abstract class.
|
||||||
|
/// </remarks>
|
||||||
|
public abstract class WebSocketContext
|
||||||
|
{
|
||||||
|
#region Protected Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="WebSocketContext"/> class.
|
||||||
|
/// </summary>
|
||||||
|
protected WebSocketContext()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the HTTP cookies included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="System.Net.CookieCollection"/> that contains the cookies.
|
||||||
|
/// </value>
|
||||||
|
public abstract CookieCollection CookieCollection { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the HTTP headers included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="QueryParamCollection"/> that contains the headers.
|
||||||
|
/// </value>
|
||||||
|
public abstract QueryParamCollection Headers { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the Host header included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that represents the value of the Host header.
|
||||||
|
/// </value>
|
||||||
|
public abstract string Host { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the client is authenticated.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public abstract bool IsAuthenticated { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the client connected from the local computer.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if the client connected from the local computer; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public abstract bool IsLocal { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the WebSocket connection is secured.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if the connection is secured; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public abstract bool IsSecureConnection { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the request is a WebSocket connection request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public abstract bool IsWebSocketRequest { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the Origin header included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that represents the value of the Origin header.
|
||||||
|
/// </value>
|
||||||
|
public abstract string Origin { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the query string included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="QueryParamCollection"/> that contains the query string parameters.
|
||||||
|
/// </value>
|
||||||
|
public abstract QueryParamCollection QueryString { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the URI requested by the client.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="Uri"/> that represents the requested URI.
|
||||||
|
/// </value>
|
||||||
|
public abstract Uri RequestUri { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the Sec-WebSocket-Key header included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property provides a part of the information used by the server to prove that it
|
||||||
|
/// received a valid WebSocket connection request.
|
||||||
|
/// </remarks>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Key header.
|
||||||
|
/// </value>
|
||||||
|
public abstract string SecWebSocketKey { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the values of the Sec-WebSocket-Protocol header included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property represents the subprotocols requested by the client.
|
||||||
|
/// </remarks>
|
||||||
|
/// <value>
|
||||||
|
/// An <see cref="T:System.Collections.Generic.IEnumerable{string}"/> instance that provides
|
||||||
|
/// an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol
|
||||||
|
/// header.
|
||||||
|
/// </value>
|
||||||
|
public abstract IEnumerable<string> SecWebSocketProtocols { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the Sec-WebSocket-Version header included in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property represents the WebSocket protocol version.
|
||||||
|
/// </remarks>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Version header.
|
||||||
|
/// </value>
|
||||||
|
public abstract string SecWebSocketVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the server endpoint as an IP address and a port number.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="System.Net.IPEndPoint"/> that represents the server endpoint.
|
||||||
|
/// </value>
|
||||||
|
public abstract IpEndPointInfo ServerEndPoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the client information (identity, authentication, and security roles).
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="IPrincipal"/> that represents the client information.
|
||||||
|
/// </value>
|
||||||
|
public abstract IPrincipal User { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the client endpoint as an IP address and a port number.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="System.Net.IPEndPoint"/> that represents the client endpoint.
|
||||||
|
/// </value>
|
||||||
|
public abstract IpEndPointInfo UserEndPoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="SocketHttpListener.WebSocket"/> instance used for two-way communication
|
||||||
|
/// between client and server.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// A <see cref="SocketHttpListener.WebSocket"/>.
|
||||||
|
/// </value>
|
||||||
|
public abstract WebSocket WebSocket { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
43
SocketHttpListener.Portable/Opcode.cs
Normal file
43
SocketHttpListener.Portable/Opcode.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the values of the opcode that indicates the type of a WebSocket frame.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The values of the opcode are defined in
|
||||||
|
/// <see href="http://tools.ietf.org/html/rfc6455#section-5.2">Section 5.2</see> of RFC 6455.
|
||||||
|
/// </remarks>
|
||||||
|
public enum Opcode : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to numeric value 0.
|
||||||
|
/// Indicates a continuation frame.
|
||||||
|
/// </summary>
|
||||||
|
Cont = 0x0,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to numeric value 1.
|
||||||
|
/// Indicates a text frame.
|
||||||
|
/// </summary>
|
||||||
|
Text = 0x1,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to numeric value 2.
|
||||||
|
/// Indicates a binary frame.
|
||||||
|
/// </summary>
|
||||||
|
Binary = 0x2,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to numeric value 8.
|
||||||
|
/// Indicates a connection close frame.
|
||||||
|
/// </summary>
|
||||||
|
Close = 0x8,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to numeric value 9.
|
||||||
|
/// Indicates a ping frame.
|
||||||
|
/// </summary>
|
||||||
|
Ping = 0x9,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to numeric value 10.
|
||||||
|
/// Indicates a pong frame.
|
||||||
|
/// </summary>
|
||||||
|
Pong = 0xa
|
||||||
|
}
|
||||||
|
}
|
149
SocketHttpListener.Portable/PayloadData.cs
Normal file
149
SocketHttpListener.Portable/PayloadData.cs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
internal class PayloadData : IEnumerable<byte>
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private byte [] _applicationData;
|
||||||
|
private byte [] _extensionData;
|
||||||
|
private bool _masked;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Const Fields
|
||||||
|
|
||||||
|
public const ulong MaxLength = long.MaxValue;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Constructors
|
||||||
|
|
||||||
|
public PayloadData ()
|
||||||
|
: this (new byte [0], new byte [0], false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PayloadData (byte [] applicationData)
|
||||||
|
: this (new byte [0], applicationData, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PayloadData (string applicationData)
|
||||||
|
: this (new byte [0], Encoding.UTF8.GetBytes (applicationData), false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PayloadData (byte [] applicationData, bool masked)
|
||||||
|
: this (new byte [0], applicationData, masked)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PayloadData (byte [] extensionData, byte [] applicationData, bool masked)
|
||||||
|
{
|
||||||
|
_extensionData = extensionData;
|
||||||
|
_applicationData = applicationData;
|
||||||
|
_masked = masked;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Properties
|
||||||
|
|
||||||
|
internal bool ContainsReservedCloseStatusCode {
|
||||||
|
get {
|
||||||
|
return _applicationData.Length > 1 &&
|
||||||
|
_applicationData.SubArray (0, 2).ToUInt16 (ByteOrder.Big).IsReserved ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
public byte [] ApplicationData {
|
||||||
|
get {
|
||||||
|
return _applicationData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte [] ExtensionData {
|
||||||
|
get {
|
||||||
|
return _extensionData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMasked {
|
||||||
|
get {
|
||||||
|
return _masked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Length {
|
||||||
|
get {
|
||||||
|
return (ulong) (_extensionData.Length + _applicationData.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private static void mask (byte [] src, byte [] key)
|
||||||
|
{
|
||||||
|
for (long i = 0; i < src.Length; i++)
|
||||||
|
src [i] = (byte) (src [i] ^ key [i % 4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
public IEnumerator<byte> GetEnumerator ()
|
||||||
|
{
|
||||||
|
foreach (byte b in _extensionData)
|
||||||
|
yield return b;
|
||||||
|
|
||||||
|
foreach (byte b in _applicationData)
|
||||||
|
yield return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Mask (byte [] maskingKey)
|
||||||
|
{
|
||||||
|
if (_extensionData.Length > 0)
|
||||||
|
mask (_extensionData, maskingKey);
|
||||||
|
|
||||||
|
if (_applicationData.Length > 0)
|
||||||
|
mask (_applicationData, maskingKey);
|
||||||
|
|
||||||
|
_masked = !_masked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte [] ToByteArray ()
|
||||||
|
{
|
||||||
|
return _extensionData.Length > 0
|
||||||
|
? new List<byte> (this).ToArray ()
|
||||||
|
: _applicationData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString ()
|
||||||
|
{
|
||||||
|
return BitConverter.ToString (ToByteArray ());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Explicitly Implemented Interface Members
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator ()
|
||||||
|
{
|
||||||
|
return GetEnumerator ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Primitives
|
||||||
|
{
|
||||||
|
public class HttpListenerException : Exception
|
||||||
|
{
|
||||||
|
public HttpListenerException(int statusCode, string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
SocketHttpListener.Portable/Primitives/ICertificate.cs
Normal file
12
SocketHttpListener.Portable/Primitives/ICertificate.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Primitives
|
||||||
|
{
|
||||||
|
public interface ICertificate
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
18
SocketHttpListener.Portable/Primitives/IStreamFactory.cs
Normal file
18
SocketHttpListener.Portable/Primitives/IStreamFactory.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Primitives
|
||||||
|
{
|
||||||
|
public interface IStreamFactory
|
||||||
|
{
|
||||||
|
Stream CreateNetworkStream(ISocket socket, bool ownsSocket);
|
||||||
|
Stream CreateSslStream(Stream innerStream, bool leaveInnerStreamOpen);
|
||||||
|
|
||||||
|
Task AuthenticateSslStreamAsServer(Stream stream, ICertificate certificate);
|
||||||
|
}
|
||||||
|
}
|
17
SocketHttpListener.Portable/Primitives/ITextEncoding.cs
Normal file
17
SocketHttpListener.Portable/Primitives/ITextEncoding.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Text;
|
||||||
|
|
||||||
|
namespace SocketHttpListener.Primitives
|
||||||
|
{
|
||||||
|
public static class TextEncodingExtensions
|
||||||
|
{
|
||||||
|
public static Encoding GetDefaultEncoding(this ITextEncoding encoding)
|
||||||
|
{
|
||||||
|
return Encoding.UTF8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
SocketHttpListener.Portable/Properties/AssemblyInfo.cs
Normal file
30
SocketHttpListener.Portable/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System.Resources;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("SocketHttpListener.Portable")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("SocketHttpListener.Portable")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
[assembly: NeutralResourcesLanguage("en")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
8
SocketHttpListener.Portable/Rsv.cs
Normal file
8
SocketHttpListener.Portable/Rsv.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
internal enum Rsv : byte
|
||||||
|
{
|
||||||
|
Off = 0x0,
|
||||||
|
On = 0x1
|
||||||
|
}
|
||||||
|
}
|
109
SocketHttpListener.Portable/SocketHttpListener.Portable.csproj
Normal file
109
SocketHttpListener.Portable/SocketHttpListener.Portable.csproj
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>SocketHttpListener.Portable</RootNamespace>
|
||||||
|
<AssemblyName>SocketHttpListener.Portable</AssemblyName>
|
||||||
|
<DefaultLanguage>en-US</DefaultLanguage>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||||
|
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
|
||||||
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||||
|
</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>
|
||||||
|
<Compile Include="ByteOrder.cs" />
|
||||||
|
<Compile Include="CloseEventArgs.cs" />
|
||||||
|
<Compile Include="CloseStatusCode.cs" />
|
||||||
|
<Compile Include="CompressionMethod.cs" />
|
||||||
|
<Compile Include="ErrorEventArgs.cs" />
|
||||||
|
<Compile Include="Ext.cs" />
|
||||||
|
<Compile Include="Fin.cs" />
|
||||||
|
<Compile Include="HttpBase.cs" />
|
||||||
|
<Compile Include="HttpResponse.cs" />
|
||||||
|
<Compile Include="Mask.cs" />
|
||||||
|
<Compile Include="MessageEventArgs.cs" />
|
||||||
|
<Compile Include="Net\AuthenticationSchemeSelector.cs" />
|
||||||
|
<Compile Include="Net\ChunkedInputStream.cs" />
|
||||||
|
<Compile Include="Net\ChunkStream.cs" />
|
||||||
|
<Compile Include="Net\CookieHelper.cs" />
|
||||||
|
<Compile Include="Net\EndPointListener.cs" />
|
||||||
|
<Compile Include="Net\EndPointManager.cs" />
|
||||||
|
<Compile Include="Net\HttpConnection.cs" />
|
||||||
|
<Compile Include="Net\HttpListener.cs" />
|
||||||
|
<Compile Include="Net\HttpListenerBasicIdentity.cs" />
|
||||||
|
<Compile Include="Net\HttpListenerContext.cs" />
|
||||||
|
<Compile Include="Net\HttpListenerPrefixCollection.cs" />
|
||||||
|
<Compile Include="Net\HttpListenerRequest.cs" />
|
||||||
|
<Compile Include="Net\HttpListenerResponse.cs" />
|
||||||
|
<Compile Include="Net\HttpStatusCode.cs" />
|
||||||
|
<Compile Include="Net\HttpStreamAsyncResult.cs" />
|
||||||
|
<Compile Include="Net\HttpVersion.cs" />
|
||||||
|
<Compile Include="Net\ListenerPrefix.cs" />
|
||||||
|
<Compile Include="Net\RequestStream.cs" />
|
||||||
|
<Compile Include="Net\ResponseStream.cs" />
|
||||||
|
<Compile Include="Net\WebHeaderCollection.cs" />
|
||||||
|
<Compile Include="Net\WebSockets\HttpListenerWebSocketContext.cs" />
|
||||||
|
<Compile Include="Net\WebSockets\WebSocketContext.cs" />
|
||||||
|
<Compile Include="Opcode.cs" />
|
||||||
|
<Compile Include="PayloadData.cs" />
|
||||||
|
<Compile Include="Primitives\HttpListenerException.cs" />
|
||||||
|
<Compile Include="Primitives\ICertificate.cs" />
|
||||||
|
<Compile Include="Primitives\IStreamFactory.cs" />
|
||||||
|
<Compile Include="Primitives\ITextEncoding.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Rsv.cs" />
|
||||||
|
<Compile Include="WebSocket.cs" />
|
||||||
|
<Compile Include="WebSocketException.cs" />
|
||||||
|
<Compile Include="WebSocketFrame.cs" />
|
||||||
|
<Compile Include="WebSocketState.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||||
|
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
|
||||||
|
<Name>MediaBrowser.Common</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
|
||||||
|
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
|
||||||
|
<Name>MediaBrowser.Model</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<PostBuildEvent>if $(ConfigurationName) == Release (
|
||||||
|
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
|
||||||
|
)</PostBuildEvent>
|
||||||
|
</PropertyGroup>
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
</Project>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Target Name="EmitMSBuildWarning" BeforeTargets="Build">
|
||||||
|
<Warning Text="Packages containing MSBuild targets and props files cannot be fully installed in projects targeting multiple frameworks. The MSBuild targets and props files have been ignored." />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
898
SocketHttpListener.Portable/WebSocket.cs
Normal file
898
SocketHttpListener.Portable/WebSocket.cs
Normal file
@ -0,0 +1,898 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using SocketHttpListener.Net.WebSockets;
|
||||||
|
using SocketHttpListener.Primitives;
|
||||||
|
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
|
||||||
|
|
||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements the WebSocket interface.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The WebSocket class provides a set of methods and properties for two-way communication using
|
||||||
|
/// the WebSocket protocol (<see href="http://tools.ietf.org/html/rfc6455">RFC 6455</see>).
|
||||||
|
/// </remarks>
|
||||||
|
public class WebSocket : IDisposable
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private string _base64Key;
|
||||||
|
private Action _closeContext;
|
||||||
|
private CompressionMethod _compression;
|
||||||
|
private WebSocketContext _context;
|
||||||
|
private CookieCollection _cookies;
|
||||||
|
private string _extensions;
|
||||||
|
private AutoResetEvent _exitReceiving;
|
||||||
|
private object _forConn;
|
||||||
|
private object _forEvent;
|
||||||
|
private object _forMessageEventQueue;
|
||||||
|
private object _forSend;
|
||||||
|
private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
private Func<WebSocketContext, string>
|
||||||
|
_handshakeRequestChecker;
|
||||||
|
private Queue<MessageEventArgs> _messageEventQueue;
|
||||||
|
private uint _nonceCount;
|
||||||
|
private string _origin;
|
||||||
|
private bool _preAuth;
|
||||||
|
private string _protocol;
|
||||||
|
private string[] _protocols;
|
||||||
|
private Uri _proxyUri;
|
||||||
|
private volatile WebSocketState _readyState;
|
||||||
|
private AutoResetEvent _receivePong;
|
||||||
|
private bool _secure;
|
||||||
|
private Stream _stream;
|
||||||
|
private Uri _uri;
|
||||||
|
private const string _version = "13";
|
||||||
|
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||||
|
|
||||||
|
private readonly ICryptoProvider _cryptoProvider;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Fields
|
||||||
|
|
||||||
|
internal const int FragmentLength = 1016; // Max value is int.MaxValue - 14.
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Constructors
|
||||||
|
|
||||||
|
// As server
|
||||||
|
internal WebSocket(HttpListenerWebSocketContext context, string protocol, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_protocol = protocol;
|
||||||
|
_cryptoProvider = cryptoProvider;
|
||||||
|
_memoryStreamFactory = memoryStreamFactory;
|
||||||
|
|
||||||
|
_closeContext = context.Close;
|
||||||
|
_secure = context.IsSecureConnection;
|
||||||
|
_stream = context.Stream;
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// As server
|
||||||
|
internal Func<WebSocketContext, string> CustomHandshakeRequestChecker
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _handshakeRequestChecker ?? (context => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_handshakeRequestChecker = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsConnected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state of the WebSocket connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// One of the <see cref="WebSocketState"/> enum values, indicates the state of the WebSocket
|
||||||
|
/// connection. The default value is <see cref="WebSocketState.Connecting"/>.
|
||||||
|
/// </value>
|
||||||
|
public WebSocketState ReadyState
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _readyState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Public Events
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the WebSocket connection has been closed.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<CloseEventArgs> OnClose;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the <see cref="WebSocket"/> gets an error.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ErrorEventArgs> OnError;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the <see cref="WebSocket"/> receives a message.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<MessageEventArgs> OnMessage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the WebSocket connection has been established.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler OnOpen;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
// As server
|
||||||
|
private bool acceptHandshake()
|
||||||
|
{
|
||||||
|
var msg = checkIfValidHandshakeRequest(_context);
|
||||||
|
if (msg != null)
|
||||||
|
{
|
||||||
|
error("An error has occurred while connecting: " + msg);
|
||||||
|
Close(HttpStatusCode.BadRequest);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_protocol != null &&
|
||||||
|
!_context.SecWebSocketProtocols.Contains(protocol => protocol == _protocol))
|
||||||
|
_protocol = null;
|
||||||
|
|
||||||
|
////var extensions = _context.Headers["Sec-WebSocket-Extensions"];
|
||||||
|
////if (extensions != null && extensions.Length > 0)
|
||||||
|
//// processSecWebSocketExtensionsHeader(extensions);
|
||||||
|
|
||||||
|
return sendHttpResponse(createHandshakeResponse());
|
||||||
|
}
|
||||||
|
|
||||||
|
// As server
|
||||||
|
private string checkIfValidHandshakeRequest(WebSocketContext context)
|
||||||
|
{
|
||||||
|
var headers = context.Headers;
|
||||||
|
return context.RequestUri == null
|
||||||
|
? "Invalid request url."
|
||||||
|
: !context.IsWebSocketRequest
|
||||||
|
? "Not WebSocket connection request."
|
||||||
|
: !validateSecWebSocketKeyHeader(headers["Sec-WebSocket-Key"])
|
||||||
|
? "Invalid Sec-WebSocket-Key header."
|
||||||
|
: !validateSecWebSocketVersionClientHeader(headers["Sec-WebSocket-Version"])
|
||||||
|
? "Invalid Sec-WebSocket-Version header."
|
||||||
|
: CustomHandshakeRequestChecker(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(CloseStatusCode code, string reason, bool wait)
|
||||||
|
{
|
||||||
|
close(new PayloadData(((ushort)code).Append(reason)), !code.IsReserved(), wait);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(PayloadData payload, bool send, bool wait)
|
||||||
|
{
|
||||||
|
lock (_forConn)
|
||||||
|
{
|
||||||
|
if (_readyState == WebSocketState.Closing || _readyState == WebSocketState.Closed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_readyState = WebSocketState.Closing;
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = new CloseEventArgs(payload);
|
||||||
|
e.WasClean =
|
||||||
|
closeHandshake(
|
||||||
|
send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null,
|
||||||
|
wait ? 1000 : 0,
|
||||||
|
closeServerResources);
|
||||||
|
|
||||||
|
_readyState = WebSocketState.Closed;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnClose.Emit(this, e);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error("An exception has occurred while OnClose.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool closeHandshake(byte[] frameAsBytes, int millisecondsTimeout, Action release)
|
||||||
|
{
|
||||||
|
var sent = frameAsBytes != null && writeBytes(frameAsBytes);
|
||||||
|
var received =
|
||||||
|
millisecondsTimeout == 0 ||
|
||||||
|
(sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout));
|
||||||
|
|
||||||
|
release();
|
||||||
|
if (_receivePong != null)
|
||||||
|
{
|
||||||
|
_receivePong.Dispose();
|
||||||
|
_receivePong = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_exitReceiving != null)
|
||||||
|
{
|
||||||
|
_exitReceiving.Dispose();
|
||||||
|
_exitReceiving = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = sent && received;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As server
|
||||||
|
private void closeServerResources()
|
||||||
|
{
|
||||||
|
if (_closeContext == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_closeContext();
|
||||||
|
_closeContext = null;
|
||||||
|
_stream = null;
|
||||||
|
_context = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool concatenateFragmentsInto(Stream dest)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var frame = WebSocketFrame.Read(_stream, true);
|
||||||
|
if (frame.IsFinal)
|
||||||
|
{
|
||||||
|
/* FINAL */
|
||||||
|
|
||||||
|
// CONT
|
||||||
|
if (frame.IsContinuation)
|
||||||
|
{
|
||||||
|
dest.WriteBytes(frame.PayloadData.ApplicationData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PING
|
||||||
|
if (frame.IsPing)
|
||||||
|
{
|
||||||
|
processPingFrame(frame);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PONG
|
||||||
|
if (frame.IsPong)
|
||||||
|
{
|
||||||
|
processPongFrame(frame);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLOSE
|
||||||
|
if (frame.IsClose)
|
||||||
|
return processCloseFrame(frame);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* MORE */
|
||||||
|
|
||||||
|
// CONT
|
||||||
|
if (frame.IsContinuation)
|
||||||
|
{
|
||||||
|
dest.WriteBytes(frame.PayloadData.ApplicationData);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ?
|
||||||
|
return processUnsupportedFrame(
|
||||||
|
frame,
|
||||||
|
CloseStatusCode.IncorrectData,
|
||||||
|
"An incorrect data has been received while receiving fragmented data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As server
|
||||||
|
private HttpResponse createHandshakeCloseResponse(HttpStatusCode code)
|
||||||
|
{
|
||||||
|
var res = HttpResponse.CreateCloseResponse(code);
|
||||||
|
res.Headers["Sec-WebSocket-Version"] = _version;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As server
|
||||||
|
private HttpResponse createHandshakeResponse()
|
||||||
|
{
|
||||||
|
var res = HttpResponse.CreateWebSocketResponse();
|
||||||
|
|
||||||
|
var headers = res.Headers;
|
||||||
|
headers["Sec-WebSocket-Accept"] = CreateResponseKey(_base64Key);
|
||||||
|
|
||||||
|
if (_protocol != null)
|
||||||
|
headers["Sec-WebSocket-Protocol"] = _protocol;
|
||||||
|
|
||||||
|
if (_extensions != null)
|
||||||
|
headers["Sec-WebSocket-Extensions"] = _extensions;
|
||||||
|
|
||||||
|
if (_cookies.Count > 0)
|
||||||
|
res.SetCookies(_cookies);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageEventArgs dequeueFromMessageEventQueue()
|
||||||
|
{
|
||||||
|
lock (_forMessageEventQueue)
|
||||||
|
return _messageEventQueue.Count > 0
|
||||||
|
? _messageEventQueue.Dequeue()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enqueueToMessageEventQueue(MessageEventArgs e)
|
||||||
|
{
|
||||||
|
lock (_forMessageEventQueue)
|
||||||
|
_messageEventQueue.Enqueue(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void error(string message, Exception exception)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
message += ". Exception.Message: " + exception.Message;
|
||||||
|
}
|
||||||
|
OnError.Emit(this, new ErrorEventArgs(message));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void error(string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnError.Emit(this, new ErrorEventArgs(message));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
_compression = CompressionMethod.None;
|
||||||
|
_cookies = new CookieCollection();
|
||||||
|
_forConn = new object();
|
||||||
|
_forEvent = new object();
|
||||||
|
_forSend = new object();
|
||||||
|
_messageEventQueue = new Queue<MessageEventArgs>();
|
||||||
|
_forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
|
||||||
|
_readyState = WebSocketState.Connecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void open()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
startReceiving();
|
||||||
|
|
||||||
|
lock (_forEvent)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnOpen.Emit(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
processException(ex, "An exception has occurred while OnOpen.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
processException(ex, "An exception has occurred while opening.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool processCloseFrame(WebSocketFrame frame)
|
||||||
|
{
|
||||||
|
var payload = frame.PayloadData;
|
||||||
|
close(payload, !payload.ContainsReservedCloseStatusCode, false);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool processDataFrame(WebSocketFrame frame)
|
||||||
|
{
|
||||||
|
var e = frame.IsCompressed
|
||||||
|
? new MessageEventArgs(
|
||||||
|
frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression))
|
||||||
|
: new MessageEventArgs(frame.Opcode, frame.PayloadData);
|
||||||
|
|
||||||
|
enqueueToMessageEventQueue(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processException(Exception exception, string message)
|
||||||
|
{
|
||||||
|
var code = CloseStatusCode.Abnormal;
|
||||||
|
var reason = message;
|
||||||
|
if (exception is WebSocketException)
|
||||||
|
{
|
||||||
|
var wsex = (WebSocketException)exception;
|
||||||
|
code = wsex.Code;
|
||||||
|
reason = wsex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
error(message ?? code.GetMessage(), exception);
|
||||||
|
if (_readyState == WebSocketState.Connecting)
|
||||||
|
Close(HttpStatusCode.BadRequest);
|
||||||
|
else
|
||||||
|
close(code, reason ?? code.GetMessage(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool processFragmentedFrame(WebSocketFrame frame)
|
||||||
|
{
|
||||||
|
return frame.IsContinuation // Not first fragment
|
||||||
|
? true
|
||||||
|
: processFragments(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool processFragments(WebSocketFrame first)
|
||||||
|
{
|
||||||
|
using (var buff = _memoryStreamFactory.CreateNew())
|
||||||
|
{
|
||||||
|
buff.WriteBytes(first.PayloadData.ApplicationData);
|
||||||
|
if (!concatenateFragmentsInto(buff))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
byte[] data;
|
||||||
|
if (_compression != CompressionMethod.None)
|
||||||
|
{
|
||||||
|
data = buff.DecompressToArray(_compression);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = buff.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueToMessageEventQueue(new MessageEventArgs(first.Opcode, data));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool processPingFrame(WebSocketFrame frame)
|
||||||
|
{
|
||||||
|
var mask = Mask.Unmask;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool processPongFrame(WebSocketFrame frame)
|
||||||
|
{
|
||||||
|
_receivePong.Set();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool processUnsupportedFrame(WebSocketFrame frame, CloseStatusCode code, string reason)
|
||||||
|
{
|
||||||
|
processException(new WebSocketException(code, reason), null);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool processWebSocketFrame(WebSocketFrame frame)
|
||||||
|
{
|
||||||
|
return frame.IsCompressed && _compression == CompressionMethod.None
|
||||||
|
? processUnsupportedFrame(
|
||||||
|
frame,
|
||||||
|
CloseStatusCode.IncorrectData,
|
||||||
|
"A compressed data has been received without available decompression method.")
|
||||||
|
: frame.IsFragmented
|
||||||
|
? processFragmentedFrame(frame)
|
||||||
|
: frame.IsData
|
||||||
|
? processDataFrame(frame)
|
||||||
|
: frame.IsPing
|
||||||
|
? processPingFrame(frame)
|
||||||
|
: frame.IsPong
|
||||||
|
? processPongFrame(frame)
|
||||||
|
: frame.IsClose
|
||||||
|
? processCloseFrame(frame)
|
||||||
|
: processUnsupportedFrame(frame, CloseStatusCode.PolicyViolation, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool send(Opcode opcode, Stream stream)
|
||||||
|
{
|
||||||
|
lock (_forSend)
|
||||||
|
{
|
||||||
|
var src = stream;
|
||||||
|
var compressed = false;
|
||||||
|
var sent = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_compression != CompressionMethod.None)
|
||||||
|
{
|
||||||
|
stream = stream.Compress(_compression);
|
||||||
|
compressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sent = send(opcode, Mask.Unmask, stream, compressed);
|
||||||
|
if (!sent)
|
||||||
|
error("Sending a data has been interrupted.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error("An exception has occurred while sending a data.", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (compressed)
|
||||||
|
stream.Dispose();
|
||||||
|
|
||||||
|
src.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool send(Opcode opcode, Mask mask, Stream stream, bool compressed)
|
||||||
|
{
|
||||||
|
var len = stream.Length;
|
||||||
|
|
||||||
|
/* Not fragmented */
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
return send(Fin.Final, opcode, mask, new byte[0], compressed);
|
||||||
|
|
||||||
|
var quo = len / FragmentLength;
|
||||||
|
var rem = (int)(len % FragmentLength);
|
||||||
|
|
||||||
|
byte[] buff = null;
|
||||||
|
if (quo == 0)
|
||||||
|
{
|
||||||
|
buff = new byte[rem];
|
||||||
|
return stream.Read(buff, 0, rem) == rem &&
|
||||||
|
send(Fin.Final, opcode, mask, buff, compressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
buff = new byte[FragmentLength];
|
||||||
|
if (quo == 1 && rem == 0)
|
||||||
|
return stream.Read(buff, 0, FragmentLength) == FragmentLength &&
|
||||||
|
send(Fin.Final, opcode, mask, buff, compressed);
|
||||||
|
|
||||||
|
/* Send fragmented */
|
||||||
|
|
||||||
|
// Begin
|
||||||
|
if (stream.Read(buff, 0, FragmentLength) != FragmentLength ||
|
||||||
|
!send(Fin.More, opcode, mask, buff, compressed))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var n = rem == 0 ? quo - 2 : quo - 1;
|
||||||
|
for (long i = 0; i < n; i++)
|
||||||
|
if (stream.Read(buff, 0, FragmentLength) != FragmentLength ||
|
||||||
|
!send(Fin.More, Opcode.Cont, mask, buff, compressed))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// End
|
||||||
|
if (rem == 0)
|
||||||
|
rem = FragmentLength;
|
||||||
|
else
|
||||||
|
buff = new byte[rem];
|
||||||
|
|
||||||
|
return stream.Read(buff, 0, rem) == rem &&
|
||||||
|
send(Fin.Final, Opcode.Cont, mask, buff, compressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool send(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)
|
||||||
|
{
|
||||||
|
lock (_forConn)
|
||||||
|
{
|
||||||
|
if (_readyState != WebSocketState.Open)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeBytes(
|
||||||
|
WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendAsync(Opcode opcode, Stream stream, Action<bool> completed)
|
||||||
|
{
|
||||||
|
Func<Opcode, Stream, bool> sender = send;
|
||||||
|
sender.BeginInvoke(
|
||||||
|
opcode,
|
||||||
|
stream,
|
||||||
|
ar =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sent = sender.EndInvoke(ar);
|
||||||
|
if (completed != null)
|
||||||
|
completed(sent);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error("An exception has occurred while callback.", ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// As server
|
||||||
|
private bool sendHttpResponse(HttpResponse response)
|
||||||
|
{
|
||||||
|
return writeBytes(response.ToByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startReceiving()
|
||||||
|
{
|
||||||
|
if (_messageEventQueue.Count > 0)
|
||||||
|
_messageEventQueue.Clear();
|
||||||
|
|
||||||
|
_exitReceiving = new AutoResetEvent(false);
|
||||||
|
_receivePong = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
Action receive = null;
|
||||||
|
receive = () => WebSocketFrame.ReadAsync(
|
||||||
|
_stream,
|
||||||
|
true,
|
||||||
|
frame =>
|
||||||
|
{
|
||||||
|
if (processWebSocketFrame(frame) && _readyState != WebSocketState.Closed)
|
||||||
|
{
|
||||||
|
receive();
|
||||||
|
|
||||||
|
if (!frame.IsData)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (_forEvent)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var e = dequeueFromMessageEventQueue();
|
||||||
|
if (e != null && _readyState == WebSocketState.Open)
|
||||||
|
OnMessage.Emit(this, e);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
processException(ex, "An exception has occurred while OnMessage.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_exitReceiving != null)
|
||||||
|
{
|
||||||
|
_exitReceiving.Set();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ex => processException(ex, "An exception has occurred while receiving a message."));
|
||||||
|
|
||||||
|
receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
// As server
|
||||||
|
private bool validateSecWebSocketKeyHeader(string value)
|
||||||
|
{
|
||||||
|
if (value == null || value.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_base64Key = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As server
|
||||||
|
private bool validateSecWebSocketVersionClientHeader(string value)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
//return value != null && value == _version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool writeBytes(byte[] data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_stream.Write(data, 0, data.Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Methods
|
||||||
|
|
||||||
|
// As server
|
||||||
|
internal void Close(HttpResponse response)
|
||||||
|
{
|
||||||
|
_readyState = WebSocketState.Closing;
|
||||||
|
|
||||||
|
sendHttpResponse(response);
|
||||||
|
closeServerResources();
|
||||||
|
|
||||||
|
_readyState = WebSocketState.Closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As server
|
||||||
|
internal void Close(HttpStatusCode code)
|
||||||
|
{
|
||||||
|
Close(createHandshakeCloseResponse(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
// As server
|
||||||
|
public void ConnectAsServer()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (acceptHandshake())
|
||||||
|
{
|
||||||
|
_readyState = WebSocketState.Open;
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
processException(ex, "An exception has occurred while connecting.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateResponseKey(string base64Key)
|
||||||
|
{
|
||||||
|
var buff = new StringBuilder(base64Key, 64);
|
||||||
|
buff.Append(_guid);
|
||||||
|
var src = _cryptoProvider.ComputeSHA1(Encoding.UTF8.GetBytes(buff.ToString()));
|
||||||
|
|
||||||
|
return Convert.ToBase64String(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the WebSocket connection, and releases all associated resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
var msg = _readyState.CheckIfClosable();
|
||||||
|
if (msg != null)
|
||||||
|
{
|
||||||
|
error(msg);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var send = _readyState == WebSocketState.Open;
|
||||||
|
close(new PayloadData(), send, send);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the WebSocket connection with the specified <see cref="CloseStatusCode"/>
|
||||||
|
/// and <see cref="string"/>, and releases all associated resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method emits a <see cref="OnError"/> event if the size
|
||||||
|
/// of <paramref name="reason"/> is greater than 123 bytes.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="code">
|
||||||
|
/// One of the <see cref="CloseStatusCode"/> enum values, represents the status code
|
||||||
|
/// indicating the reason for the close.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="reason">
|
||||||
|
/// A <see cref="string"/> that represents the reason for the close.
|
||||||
|
/// </param>
|
||||||
|
public void Close(CloseStatusCode code, string reason)
|
||||||
|
{
|
||||||
|
byte[] data = null;
|
||||||
|
var msg = _readyState.CheckIfClosable() ??
|
||||||
|
(data = ((ushort)code).Append(reason)).CheckIfValidControlData("reason");
|
||||||
|
|
||||||
|
if (msg != null)
|
||||||
|
{
|
||||||
|
error(msg);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var send = _readyState == WebSocketState.Open && !code.IsReserved();
|
||||||
|
close(new PayloadData(data), send, send);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a binary <paramref name="data"/> asynchronously using the WebSocket connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method doesn't wait for the send to be complete.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="data">
|
||||||
|
/// An array of <see cref="byte"/> that represents the binary data to send.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="completed">
|
||||||
|
/// An Action<bool> delegate that references the method(s) called when the send is
|
||||||
|
/// complete. A <see cref="bool"/> passed to this delegate is <c>true</c> if the send is
|
||||||
|
/// complete successfully; otherwise, <c>false</c>.
|
||||||
|
/// </param>
|
||||||
|
public void SendAsync(byte[] data, Action<bool> completed)
|
||||||
|
{
|
||||||
|
var msg = _readyState.CheckIfOpen() ?? data.CheckIfValidSendData();
|
||||||
|
if (msg != null)
|
||||||
|
{
|
||||||
|
error(msg);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAsync(Opcode.Binary, _memoryStreamFactory.CreateNew(data), completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a text <paramref name="data"/> asynchronously using the WebSocket connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method doesn't wait for the send to be complete.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="data">
|
||||||
|
/// A <see cref="string"/> that represents the text data to send.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="completed">
|
||||||
|
/// An Action<bool> delegate that references the method(s) called when the send is
|
||||||
|
/// complete. A <see cref="bool"/> passed to this delegate is <c>true</c> if the send is
|
||||||
|
/// complete successfully; otherwise, <c>false</c>.
|
||||||
|
/// </param>
|
||||||
|
public void SendAsync(string data, Action<bool> completed)
|
||||||
|
{
|
||||||
|
var msg = _readyState.CheckIfOpen() ?? data.CheckIfValidSendData();
|
||||||
|
if (msg != null)
|
||||||
|
{
|
||||||
|
error(msg);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAsync(Opcode.Text, _memoryStreamFactory.CreateNew(Encoding.UTF8.GetBytes(data)), completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Explicit Interface Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the WebSocket connection, and releases all associated resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method closes the WebSocket connection with <see cref="CloseStatusCode.Away"/>.
|
||||||
|
/// </remarks>
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
Close(CloseStatusCode.Away, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
60
SocketHttpListener.Portable/WebSocketException.cs
Normal file
60
SocketHttpListener.Portable/WebSocketException.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The exception that is thrown when a <see cref="WebSocket"/> gets a fatal error.
|
||||||
|
/// </summary>
|
||||||
|
public class WebSocketException : Exception
|
||||||
|
{
|
||||||
|
#region Internal Constructors
|
||||||
|
|
||||||
|
internal WebSocketException ()
|
||||||
|
: this (CloseStatusCode.Abnormal, null, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WebSocketException (string message)
|
||||||
|
: this (CloseStatusCode.Abnormal, message, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WebSocketException (CloseStatusCode code)
|
||||||
|
: this (code, null, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WebSocketException (string message, Exception innerException)
|
||||||
|
: this (CloseStatusCode.Abnormal, message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WebSocketException (CloseStatusCode code, string message)
|
||||||
|
: this (code, message, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WebSocketException (CloseStatusCode code, string message, Exception innerException)
|
||||||
|
: base (message ?? code.GetMessage (), innerException)
|
||||||
|
{
|
||||||
|
Code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the status code indicating the cause for the exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// One of the <see cref="CloseStatusCode"/> enum values, represents the status code indicating
|
||||||
|
/// the cause for the exception.
|
||||||
|
/// </value>
|
||||||
|
public CloseStatusCode Code {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
578
SocketHttpListener.Portable/WebSocketFrame.cs
Normal file
578
SocketHttpListener.Portable/WebSocketFrame.cs
Normal file
@ -0,0 +1,578 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
internal class WebSocketFrame : IEnumerable<byte>
|
||||||
|
{
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private byte[] _extPayloadLength;
|
||||||
|
private Fin _fin;
|
||||||
|
private Mask _mask;
|
||||||
|
private byte[] _maskingKey;
|
||||||
|
private Opcode _opcode;
|
||||||
|
private PayloadData _payloadData;
|
||||||
|
private byte _payloadLength;
|
||||||
|
private Rsv _rsv1;
|
||||||
|
private Rsv _rsv2;
|
||||||
|
private Rsv _rsv3;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Fields
|
||||||
|
|
||||||
|
internal static readonly byte[] EmptyUnmaskPingData;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Static Constructor
|
||||||
|
|
||||||
|
static WebSocketFrame()
|
||||||
|
{
|
||||||
|
EmptyUnmaskPingData = CreatePingFrame(Mask.Unmask).ToByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Constructors
|
||||||
|
|
||||||
|
private WebSocketFrame()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Constructors
|
||||||
|
|
||||||
|
internal WebSocketFrame(Opcode opcode, PayloadData payload)
|
||||||
|
: this(Fin.Final, opcode, Mask.Mask, payload, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WebSocketFrame(Opcode opcode, Mask mask, PayloadData payload)
|
||||||
|
: this(Fin.Final, opcode, mask, payload, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WebSocketFrame(Fin fin, Opcode opcode, Mask mask, PayloadData payload)
|
||||||
|
: this(fin, opcode, mask, payload, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WebSocketFrame(
|
||||||
|
Fin fin, Opcode opcode, Mask mask, PayloadData payload, bool compressed)
|
||||||
|
{
|
||||||
|
_fin = fin;
|
||||||
|
_rsv1 = isData(opcode) && compressed ? Rsv.On : Rsv.Off;
|
||||||
|
_rsv2 = Rsv.Off;
|
||||||
|
_rsv3 = Rsv.Off;
|
||||||
|
_opcode = opcode;
|
||||||
|
_mask = mask;
|
||||||
|
|
||||||
|
var len = payload.Length;
|
||||||
|
if (len < 126)
|
||||||
|
{
|
||||||
|
_payloadLength = (byte)len;
|
||||||
|
_extPayloadLength = new byte[0];
|
||||||
|
}
|
||||||
|
else if (len < 0x010000)
|
||||||
|
{
|
||||||
|
_payloadLength = (byte)126;
|
||||||
|
_extPayloadLength = ((ushort)len).ToByteArrayInternally(ByteOrder.Big);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_payloadLength = (byte)127;
|
||||||
|
_extPayloadLength = len.ToByteArrayInternally(ByteOrder.Big);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask == Mask.Mask)
|
||||||
|
{
|
||||||
|
_maskingKey = createMaskingKey();
|
||||||
|
payload.Mask(_maskingKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_maskingKey = new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
_payloadData = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
public byte[] ExtendedPayloadLength
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _extPayloadLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Fin Fin
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _fin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsBinary
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _opcode == Opcode.Binary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsClose
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _opcode == Opcode.Close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCompressed
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _rsv1 == Rsv.On;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsContinuation
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _opcode == Opcode.Cont;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsControl
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _opcode == Opcode.Close || _opcode == Opcode.Ping || _opcode == Opcode.Pong;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _opcode == Opcode.Binary || _opcode == Opcode.Text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsFinal
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _fin == Fin.Final;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsFragmented
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _fin == Fin.More || _opcode == Opcode.Cont;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMasked
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _mask == Mask.Mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPerMessageCompressed
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (_opcode == Opcode.Binary || _opcode == Opcode.Text) && _rsv1 == Rsv.On;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPing
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _opcode == Opcode.Ping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPong
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _opcode == Opcode.Pong;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _opcode == Opcode.Text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 2 + (ulong)(_extPayloadLength.Length + _maskingKey.Length) + _payloadData.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mask Mask
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] MaskingKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _maskingKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Opcode Opcode
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _opcode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PayloadData PayloadData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _payloadData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte PayloadLength
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _payloadLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rsv Rsv1
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _rsv1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rsv Rsv2
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _rsv2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rsv Rsv3
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _rsv3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private byte[] createMaskingKey()
|
||||||
|
{
|
||||||
|
var key = new byte[4];
|
||||||
|
var rand = new Random();
|
||||||
|
rand.NextBytes(key);
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool isControl(Opcode opcode)
|
||||||
|
{
|
||||||
|
return opcode == Opcode.Close || opcode == Opcode.Ping || opcode == Opcode.Pong;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool isData(Opcode opcode)
|
||||||
|
{
|
||||||
|
return opcode == Opcode.Text || opcode == Opcode.Binary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WebSocketFrame read(byte[] header, Stream stream, bool unmask)
|
||||||
|
{
|
||||||
|
/* Header */
|
||||||
|
|
||||||
|
// FIN
|
||||||
|
var fin = (header[0] & 0x80) == 0x80 ? Fin.Final : Fin.More;
|
||||||
|
// RSV1
|
||||||
|
var rsv1 = (header[0] & 0x40) == 0x40 ? Rsv.On : Rsv.Off;
|
||||||
|
// RSV2
|
||||||
|
var rsv2 = (header[0] & 0x20) == 0x20 ? Rsv.On : Rsv.Off;
|
||||||
|
// RSV3
|
||||||
|
var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off;
|
||||||
|
// Opcode
|
||||||
|
var opcode = (Opcode)(header[0] & 0x0f);
|
||||||
|
// MASK
|
||||||
|
var mask = (header[1] & 0x80) == 0x80 ? Mask.Mask : Mask.Unmask;
|
||||||
|
// Payload Length
|
||||||
|
var payloadLen = (byte)(header[1] & 0x7f);
|
||||||
|
|
||||||
|
// Check if correct frame.
|
||||||
|
var incorrect = isControl(opcode) && fin == Fin.More
|
||||||
|
? "A control frame is fragmented."
|
||||||
|
: !isData(opcode) && rsv1 == Rsv.On
|
||||||
|
? "A non data frame is compressed."
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (incorrect != null)
|
||||||
|
throw new WebSocketException(CloseStatusCode.IncorrectData, incorrect);
|
||||||
|
|
||||||
|
// Check if consistent frame.
|
||||||
|
if (isControl(opcode) && payloadLen > 125)
|
||||||
|
throw new WebSocketException(
|
||||||
|
CloseStatusCode.InconsistentData,
|
||||||
|
"The length of payload data of a control frame is greater than 125 bytes.");
|
||||||
|
|
||||||
|
var frame = new WebSocketFrame();
|
||||||
|
frame._fin = fin;
|
||||||
|
frame._rsv1 = rsv1;
|
||||||
|
frame._rsv2 = rsv2;
|
||||||
|
frame._rsv3 = rsv3;
|
||||||
|
frame._opcode = opcode;
|
||||||
|
frame._mask = mask;
|
||||||
|
frame._payloadLength = payloadLen;
|
||||||
|
|
||||||
|
/* Extended Payload Length */
|
||||||
|
|
||||||
|
var size = payloadLen < 126
|
||||||
|
? 0
|
||||||
|
: payloadLen == 126
|
||||||
|
? 2
|
||||||
|
: 8;
|
||||||
|
|
||||||
|
var extPayloadLen = size > 0 ? stream.ReadBytes(size) : new byte[0];
|
||||||
|
if (size > 0 && extPayloadLen.Length != size)
|
||||||
|
throw new WebSocketException(
|
||||||
|
"The 'Extended Payload Length' of a frame cannot be read from the data source.");
|
||||||
|
|
||||||
|
frame._extPayloadLength = extPayloadLen;
|
||||||
|
|
||||||
|
/* Masking Key */
|
||||||
|
|
||||||
|
var masked = mask == Mask.Mask;
|
||||||
|
var maskingKey = masked ? stream.ReadBytes(4) : new byte[0];
|
||||||
|
if (masked && maskingKey.Length != 4)
|
||||||
|
throw new WebSocketException(
|
||||||
|
"The 'Masking Key' of a frame cannot be read from the data source.");
|
||||||
|
|
||||||
|
frame._maskingKey = maskingKey;
|
||||||
|
|
||||||
|
/* Payload Data */
|
||||||
|
|
||||||
|
ulong len = payloadLen < 126
|
||||||
|
? payloadLen
|
||||||
|
: payloadLen == 126
|
||||||
|
? extPayloadLen.ToUInt16(ByteOrder.Big)
|
||||||
|
: extPayloadLen.ToUInt64(ByteOrder.Big);
|
||||||
|
|
||||||
|
byte[] data = null;
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
// Check if allowable payload data length.
|
||||||
|
if (payloadLen > 126 && len > PayloadData.MaxLength)
|
||||||
|
throw new WebSocketException(
|
||||||
|
CloseStatusCode.TooBig,
|
||||||
|
"The length of 'Payload Data' of a frame is greater than the allowable length.");
|
||||||
|
|
||||||
|
data = payloadLen > 126
|
||||||
|
? stream.ReadBytes((long)len, 1024)
|
||||||
|
: stream.ReadBytes((int)len);
|
||||||
|
|
||||||
|
//if (data.LongLength != (long)len)
|
||||||
|
// throw new WebSocketException(
|
||||||
|
// "The 'Payload Data' of a frame cannot be read from the data source.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new PayloadData(data, masked);
|
||||||
|
if (masked && unmask)
|
||||||
|
{
|
||||||
|
payload.Mask(maskingKey);
|
||||||
|
frame._mask = Mask.Unmask;
|
||||||
|
frame._maskingKey = new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
frame._payloadData = payload;
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Methods
|
||||||
|
|
||||||
|
internal static WebSocketFrame CreateCloseFrame(Mask mask, byte[] data)
|
||||||
|
{
|
||||||
|
return new WebSocketFrame(Opcode.Close, mask, new PayloadData(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static WebSocketFrame CreateCloseFrame(Mask mask, PayloadData payload)
|
||||||
|
{
|
||||||
|
return new WebSocketFrame(Opcode.Close, mask, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static WebSocketFrame CreateCloseFrame(Mask mask, CloseStatusCode code, string reason)
|
||||||
|
{
|
||||||
|
return new WebSocketFrame(
|
||||||
|
Opcode.Close, mask, new PayloadData(((ushort)code).Append(reason)));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static WebSocketFrame CreatePingFrame(Mask mask)
|
||||||
|
{
|
||||||
|
return new WebSocketFrame(Opcode.Ping, mask, new PayloadData());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static WebSocketFrame CreatePingFrame(Mask mask, byte[] data)
|
||||||
|
{
|
||||||
|
return new WebSocketFrame(Opcode.Ping, mask, new PayloadData(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static WebSocketFrame CreatePongFrame(Mask mask, PayloadData payload)
|
||||||
|
{
|
||||||
|
return new WebSocketFrame(Opcode.Pong, mask, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static WebSocketFrame CreateWebSocketFrame(
|
||||||
|
Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)
|
||||||
|
{
|
||||||
|
return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static WebSocketFrame Read(Stream stream)
|
||||||
|
{
|
||||||
|
return Read(stream, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static WebSocketFrame Read(Stream stream, bool unmask)
|
||||||
|
{
|
||||||
|
var header = stream.ReadBytes(2);
|
||||||
|
if (header.Length != 2)
|
||||||
|
throw new WebSocketException(
|
||||||
|
"The header part of a frame cannot be read from the data source.");
|
||||||
|
|
||||||
|
return read(header, stream, unmask);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static async void ReadAsync(
|
||||||
|
Stream stream, bool unmask, Action<WebSocketFrame> completed, Action<Exception> error)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
|
||||||
|
if (header.Length != 2)
|
||||||
|
throw new WebSocketException(
|
||||||
|
"The header part of a frame cannot be read from the data source.");
|
||||||
|
|
||||||
|
var frame = read(header, stream, unmask);
|
||||||
|
if (completed != null)
|
||||||
|
completed(frame);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (error != null)
|
||||||
|
{
|
||||||
|
error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
public IEnumerator<byte> GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach (var b in ToByteArray())
|
||||||
|
yield return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Print(bool dumped)
|
||||||
|
{
|
||||||
|
//Console.WriteLine(dumped ? dump(this) : print(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ToByteArray()
|
||||||
|
{
|
||||||
|
using (var buff = new MemoryStream())
|
||||||
|
{
|
||||||
|
var header = (int)_fin;
|
||||||
|
header = (header << 1) + (int)_rsv1;
|
||||||
|
header = (header << 1) + (int)_rsv2;
|
||||||
|
header = (header << 1) + (int)_rsv3;
|
||||||
|
header = (header << 4) + (int)_opcode;
|
||||||
|
header = (header << 1) + (int)_mask;
|
||||||
|
header = (header << 7) + (int)_payloadLength;
|
||||||
|
buff.Write(((ushort)header).ToByteArrayInternally(ByteOrder.Big), 0, 2);
|
||||||
|
|
||||||
|
if (_payloadLength > 125)
|
||||||
|
buff.Write(_extPayloadLength, 0, _extPayloadLength.Length);
|
||||||
|
|
||||||
|
if (_mask == Mask.Mask)
|
||||||
|
buff.Write(_maskingKey, 0, _maskingKey.Length);
|
||||||
|
|
||||||
|
if (_payloadLength > 0)
|
||||||
|
{
|
||||||
|
var payload = _payloadData.ToByteArray();
|
||||||
|
if (_payloadLength < 127)
|
||||||
|
buff.Write(payload, 0, payload.Length);
|
||||||
|
else
|
||||||
|
buff.WriteBytes(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return BitConverter.ToString(ToByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Explicitly Implemented Interface Members
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
35
SocketHttpListener.Portable/WebSocketState.cs
Normal file
35
SocketHttpListener.Portable/WebSocketState.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
namespace SocketHttpListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the values of the state of the WebSocket connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The values of the state are defined in
|
||||||
|
/// <see href="http://www.w3.org/TR/websockets/#dom-websocket-readystate">The WebSocket
|
||||||
|
/// API</see>.
|
||||||
|
/// </remarks>
|
||||||
|
public enum WebSocketState : ushort
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to numeric value 0.
|
||||||
|
/// Indicates that the connection has not yet been established.
|
||||||
|
/// </summary>
|
||||||
|
Connecting = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to numeric value 1.
|
||||||
|
/// Indicates that the connection is established and the communication is possible.
|
||||||
|
/// </summary>
|
||||||
|
Open = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to numeric value 2.
|
||||||
|
/// Indicates that the connection is going through the closing handshake or
|
||||||
|
/// the <c>WebSocket.Close</c> method has been invoked.
|
||||||
|
/// </summary>
|
||||||
|
Closing = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to numeric value 3.
|
||||||
|
/// Indicates that the connection has been closed or couldn't be opened.
|
||||||
|
/// </summary>
|
||||||
|
Closed = 3
|
||||||
|
}
|
||||||
|
}
|
5
SocketHttpListener.Portable/packages.config
Normal file
5
SocketHttpListener.Portable/packages.config
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="MediaBrowser.Common" version="3.0.689" targetFramework="portable45-net45+win8" />
|
||||||
|
<package id="Patterns.Logging" version="1.0.0.6" targetFramework="portable45-net45+win8" />
|
||||||
|
</packages>
|
17
SocketHttpListener.Portable/project.json
Normal file
17
SocketHttpListener.Portable/project.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"frameworks":{
|
||||||
|
"netstandard1.6":{
|
||||||
|
"dependencies":{
|
||||||
|
"NETStandard.Library":"1.6.0",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
".NETPortable,Version=v4.5,Profile=Profile7":{
|
||||||
|
"buildOptions": {
|
||||||
|
"define": [ ]
|
||||||
|
},
|
||||||
|
"frameworkAssemblies":{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user