瀏覽代碼

update portable projects

Luke Pulverenti 8 年之前
父節點
當前提交
406e6cb813
共有 100 個文件被更改,包括 12627 次插入211 次删除
  1. 4 9
      Emby.Drawing.ImageMagick/ImageMagickEncoder.cs
  2. 1 0
      Emby.Server.Core/Emby.Server.Core.xproj
  3. 66 56
      Emby.Server.Core/project.json
  4. 13 13
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  5. 1 2
      Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
  6. 1 2
      Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
  7. 6 7
      Emby.Server.Implementations/Library/LibraryManager.cs
  8. 1 2
      Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
  9. 3 3
      Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
  10. 3 3
      Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  11. 1 2
      Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
  12. 4 4
      Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
  13. 0 63
      Emby.Server.Implementations/Logging/PatternsLogger.cs
  14. 2 3
      Emby.Server.Implementations/packages.config
  15. 0 4
      MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
  16. 25 2
      MediaBrowser.Server.Mono/Program.cs
  17. 0 1
      MediaBrowser.Server.Mono/packages.config
  18. 1 1
      MediaBrowser.Server.Startup.Common/Cryptography/CertificateGenerator.cs
  19. 6 5
      MediaBrowser.Server.Startup.Common/ImageEncoderHelper.cs
  20. 6 14
      MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
  21. 1 3
      MediaBrowser.Server.Startup.Common/packages.config
  22. 20 4
      MediaBrowser.ServerApplication/MainStartup.cs
  23. 0 4
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  24. 0 1
      MediaBrowser.ServerApplication/packages.config
  25. 0 2
      MediaBrowser.WebDashboard/packages.config
  26. 359 0
      MediaBrowser.sln
  27. 27 0
      ServiceStack/FilterAttributeCache.cs
  28. 27 0
      ServiceStack/Host/ActionContext.cs
  29. 77 0
      ServiceStack/Host/ContentTypes.cs
  30. 95 0
      ServiceStack/Host/HttpResponseStreamWrapper.cs
  31. 200 0
      ServiceStack/Host/RestHandler.cs
  32. 443 0
      ServiceStack/Host/RestPath.cs
  33. 220 0
      ServiceStack/Host/ServiceController.cs
  34. 156 0
      ServiceStack/Host/ServiceExec.cs
  35. 27 0
      ServiceStack/Host/ServiceMetadata.cs
  36. 27 0
      ServiceStack/HttpHandlerFactory.cs
  37. 127 0
      ServiceStack/HttpRequestExtensions.cs
  38. 237 0
      ServiceStack/HttpResponseExtensionsInternal.cs
  39. 250 0
      ServiceStack/HttpResult.cs
  40. 34 0
      ServiceStack/HttpUtils.cs
  41. 25 0
      ServiceStack/Properties/AssemblyInfo.cs
  42. 270 0
      ServiceStack/ReflectionExtensions.cs
  43. 131 0
      ServiceStack/ServiceStack.csproj
  44. 0 0
      ServiceStack/ServiceStack.nuget.targets
  45. 19 0
      ServiceStack/ServiceStack.xproj
  46. 74 0
      ServiceStack/ServiceStackHost.Runtime.cs
  47. 104 0
      ServiceStack/ServiceStackHost.cs
  48. 126 0
      ServiceStack/StringMapTypeDeserializer.cs
  49. 33 0
      ServiceStack/UrlExtensions.cs
  50. 3 0
      ServiceStack/packages.config
  51. 1 1
      ServiceStack/project.json
  52. 17 0
      SocketHttpListener.Portable/ByteOrder.cs
  53. 90 0
      SocketHttpListener.Portable/CloseEventArgs.cs
  54. 94 0
      SocketHttpListener.Portable/CloseStatusCode.cs
  55. 23 0
      SocketHttpListener.Portable/CompressionMethod.cs
  56. 46 0
      SocketHttpListener.Portable/ErrorEventArgs.cs
  57. 1089 0
      SocketHttpListener.Portable/Ext.cs
  58. 8 0
      SocketHttpListener.Portable/Fin.cs
  59. 104 0
      SocketHttpListener.Portable/HttpBase.cs
  60. 161 0
      SocketHttpListener.Portable/HttpResponse.cs
  61. 8 0
      SocketHttpListener.Portable/Mask.cs
  62. 96 0
      SocketHttpListener.Portable/MessageEventArgs.cs
  63. 6 0
      SocketHttpListener.Portable/Net/AuthenticationSchemeSelector.cs
  64. 371 0
      SocketHttpListener.Portable/Net/ChunkStream.cs
  65. 160 0
      SocketHttpListener.Portable/Net/ChunkedInputStream.cs
  66. 144 0
      SocketHttpListener.Portable/Net/CookieHelper.cs
  67. 368 0
      SocketHttpListener.Portable/Net/EndPointListener.cs
  68. 165 0
      SocketHttpListener.Portable/Net/EndPointManager.cs
  69. 550 0
      SocketHttpListener.Portable/Net/HttpConnection.cs
  70. 299 0
      SocketHttpListener.Portable/Net/HttpListener.cs
  71. 70 0
      SocketHttpListener.Portable/Net/HttpListenerBasicIdentity.cs
  72. 201 0
      SocketHttpListener.Portable/Net/HttpListenerContext.cs
  73. 97 0
      SocketHttpListener.Portable/Net/HttpListenerPrefixCollection.cs
  74. 654 0
      SocketHttpListener.Portable/Net/HttpListenerRequest.cs
  75. 517 0
      SocketHttpListener.Portable/Net/HttpListenerResponse.cs
  76. 321 0
      SocketHttpListener.Portable/Net/HttpStatusCode.cs
  77. 77 0
      SocketHttpListener.Portable/Net/HttpStreamAsyncResult.cs
  78. 16 0
      SocketHttpListener.Portable/Net/HttpVersion.cs
  79. 148 0
      SocketHttpListener.Portable/Net/ListenerPrefix.cs
  80. 231 0
      SocketHttpListener.Portable/Net/RequestStream.cs
  81. 316 0
      SocketHttpListener.Portable/Net/ResponseStream.cs
  82. 391 0
      SocketHttpListener.Portable/Net/WebHeaderCollection.cs
  83. 347 0
      SocketHttpListener.Portable/Net/WebSockets/HttpListenerWebSocketContext.cs
  84. 183 0
      SocketHttpListener.Portable/Net/WebSockets/WebSocketContext.cs
  85. 43 0
      SocketHttpListener.Portable/Opcode.cs
  86. 149 0
      SocketHttpListener.Portable/PayloadData.cs
  87. 17 0
      SocketHttpListener.Portable/Primitives/HttpListenerException.cs
  88. 12 0
      SocketHttpListener.Portable/Primitives/ICertificate.cs
  89. 18 0
      SocketHttpListener.Portable/Primitives/IStreamFactory.cs
  90. 17 0
      SocketHttpListener.Portable/Primitives/ITextEncoding.cs
  91. 30 0
      SocketHttpListener.Portable/Properties/AssemblyInfo.cs
  92. 8 0
      SocketHttpListener.Portable/Rsv.cs
  93. 109 0
      SocketHttpListener.Portable/SocketHttpListener.Portable.csproj
  94. 6 0
      SocketHttpListener.Portable/SocketHttpListener.Portable.nuget.targets
  95. 898 0
      SocketHttpListener.Portable/WebSocket.cs
  96. 60 0
      SocketHttpListener.Portable/WebSocketException.cs
  97. 578 0
      SocketHttpListener.Portable/WebSocketFrame.cs
  98. 35 0
      SocketHttpListener.Portable/WebSocketState.cs
  99. 5 0
      SocketHttpListener.Portable/packages.config
  100. 17 0
      SocketHttpListener.Portable/project.json

+ 4 - 9
Emby.Drawing.ImageMagick/ImageMagickEncoder.cs

@@ -8,9 +8,6 @@ using MediaBrowser.Model.Logging;
 using System;
 using System.IO;
 using System.Linq;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.IO;
 
 namespace Emby.Drawing.ImageMagick
@@ -19,17 +16,15 @@ namespace Emby.Drawing.ImageMagick
     {
         private readonly ILogger _logger;
         private readonly IApplicationPaths _appPaths;
-        private readonly IHttpClient _httpClient;
+        private readonly Func<IHttpClient> _httpClientFactory;
         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;
             _appPaths = appPaths;
-            _httpClient = httpClient;
+            _httpClientFactory = httpClientFactory;
             _fileSystem = fileSystem;
-            _config = config;
 
             LogVersion();
         }
@@ -260,7 +255,7 @@ namespace Emby.Drawing.ImageMagick
                 {
                     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);
                 }
                 else if (options.UnplayedCount.HasValue)

+ 1 - 0
Emby.Server.Core/Emby.Server.Core.xproj

@@ -16,6 +16,7 @@
     <SchemaVersion>2.0</SchemaVersion>
   </PropertyGroup>
   <ItemGroup>
+    <ProjectReference Include="..\ServiceStack\ServiceStack.csproj" />
     <ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj" />
     <ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
     <ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />

+ 66 - 56
Emby.Server.Core/project.json

@@ -2,7 +2,7 @@
   "version": "1.0.0-*",
 
   "dependencies": {
-    
+
   },
 
   "frameworks": {
@@ -56,65 +56,75 @@
         "Emby.Drawing": {
           "target": "project"
         },
-        "SocketHttpListener.Portable": "1.0.50"    
-		}
-    },
-    "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"
-        },
-        "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": {
+        "ServiceStack": {
           "target": "project"
         },
-        "MediaBrowser.WebDashboard": {
-          "target": "project"
-        },
-        "Emby.Drawing": {
+        "SocketHttpListener.Portable": {
           "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"
+          }
+        }
       }
     }
   }

+ 13 - 13
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -163,7 +163,6 @@
     <Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
     <Compile Include="LiveTv\TunerHosts\QueueStream.cs" />
     <Compile Include="Localization\LocalizationManager.cs" />
-    <Compile Include="Logging\PatternsLogger.cs" />
     <Compile Include="MediaEncoder\EncodingManager.cs" />
     <Compile Include="News\NewsEntryPoint.cs" />
     <Compile Include="News\NewsService.cs" />
@@ -275,23 +274,24 @@
       <Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project>
       <Name>MediaBrowser.Server.Implementations</Name>
     </ProjectReference>
-    <Reference Include="ServiceStack">
-      <HintPath>..\ThirdParty\ServiceStack\ServiceStack.dll</HintPath>
-    </Reference>
-    <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>
-      <Private>True</Private>
-    </Reference>
+    <ProjectReference Include="..\ServiceStack\ServiceStack.csproj">
+      <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.0.63\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
+      <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.6151.30291, Culture=neutral, processorArchitecture=MSIL">
-      <HintPath>..\packages\MediaBrowser.Naming.1.0.0.59\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
+    <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 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>
+    <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>
       <Private>True</Private>
     </Reference>
   </ItemGroup>

+ 1 - 2
Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs

@@ -15,7 +15,6 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using Emby.Server.Implementations.Library;
-using Emby.Server.Implementations.Logging;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
@@ -71,7 +70,7 @@ namespace Emby.Server.Implementations.FileOrganization
             }
 
             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) ??
                 new MediaBrowser.Naming.TV.EpisodeInfo();

+ 1 - 2
Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs

@@ -5,7 +5,6 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
-using Emby.Server.Implementations.Logging;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.IO;
@@ -55,7 +54,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
         public void Start(IEnumerable<string> urlPrefixes)
         {
             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;
 

+ 6 - 7
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -29,7 +29,6 @@ using System.Threading;
 using System.Threading.Tasks;
 using Emby.Server.Implementations.Library.Resolvers;
 using Emby.Server.Implementations.Library.Validators;
-using Emby.Server.Implementations.Logging;
 using Emby.Server.Implementations.ScheduledTasks;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Controller.Channels;
@@ -2266,7 +2265,7 @@ namespace Emby.Server.Implementations.Library
 
         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);
         }
 
@@ -2294,7 +2293,7 @@ namespace Emby.Server.Implementations.Library
         public bool FillMissingEpisodeNumbersFromPath(Episode episode)
         {
             var resolver = new EpisodeResolver(GetNamingOptions(),
-                new PatternsLogger());
+                new NullLogger());
 
             var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd ||
                            episode.VideoType == VideoType.HdDvd;
@@ -2440,7 +2439,7 @@ namespace Emby.Server.Implementations.Library
 
         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 cleanName = resolver.CleanString(result.Name);
@@ -2459,7 +2458,7 @@ namespace Emby.Server.Implementations.Library
                 .SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
                 .ToList();
 
-            var videoListResolver = new VideoListResolver(GetNamingOptions(), new PatternsLogger());
+            var videoListResolver = new VideoListResolver(GetNamingOptions(), new NullLogger());
 
             var videos = videoListResolver.Resolve(fileSystemChildren);
 
@@ -2505,7 +2504,7 @@ namespace Emby.Server.Implementations.Library
                 .SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
                 .ToList();
 
-            var videoListResolver = new VideoListResolver(GetNamingOptions(), new PatternsLogger());
+            var videoListResolver = new VideoListResolver(GetNamingOptions(), new NullLogger());
 
             var videos = videoListResolver.Resolve(fileSystemChildren);
 
@@ -2628,7 +2627,7 @@ namespace Emby.Server.Implementations.Library
 
         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);
 

+ 1 - 2
Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs

@@ -8,7 +8,6 @@ using MediaBrowser.Naming.Audio;
 using System;
 using System.Collections.Generic;
 using System.IO;
-using Emby.Server.Implementations.Logging;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Controller.Configuration;
@@ -164,7 +163,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
         {
             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);
 
             return result.IsMultiPart;

+ 3 - 3
Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs

@@ -4,7 +4,7 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Naming.Video;
 using System;
 using System.IO;
-using Emby.Server.Implementations.Logging;
+using MediaBrowser.Model.Logging;
 
 namespace Emby.Server.Implementations.Library.Resolvers
 {
@@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
             var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
             
             // 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)
             {
@@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
         {
             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);
 
             Set3DFormat(video, result.Is3D, result.Format3D);

+ 3 - 3
Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -11,10 +11,10 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using Emby.Server.Implementations.Logging;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
 
 namespace Emby.Server.Implementations.Library.Resolvers.Movies
 {
@@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
 
             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 result = new MultiItemResolverResult
@@ -486,7 +486,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
             }
 
             var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
-            var resolver = new StackResolver(namingOptions, new PatternsLogger());
+            var resolver = new StackResolver(namingOptions, new NullLogger());
 
             var result = resolver.ResolveDirectories(folderPaths);
 

+ 1 - 2
Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs

@@ -10,7 +10,6 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using Emby.Server.Implementations.Logging;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Controller.Configuration;
@@ -171,7 +170,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
                                 .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);
                         if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue)
                         {

+ 4 - 4
Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs

@@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             }
 
             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);
             return results.Select(p => GetProgramInfo(p, info));
@@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
         {
             // Add the channel image url
             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();
 
             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
             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();
 
             // 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
             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();
 
             // Should this method be async?

+ 0 - 63
Emby.Server.Implementations/Logging/PatternsLogger.cs

@@ -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)
-        {
-        }
-    }
-}

+ 2 - 3
Emby.Server.Implementations/packages.config

@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="MediaBrowser.Naming" version="1.0.0.59" targetFramework="portable45-net45+win8" />
-  <package id="Patterns.Logging" version="1.0.0.6" targetFramework="portable45-net45+win8" />
+  <package id="Emby.XmlTv" version="1.0.1" 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="Emby.XmlTv" version="1.0.63" targetFramework="portable45-net45+win8" />
 </packages>

+ 0 - 4
MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj

@@ -72,10 +72,6 @@
       <HintPath>..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.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>
     <Reference Include="System" />
     <Reference Include="MediaBrowser.IsoMounting.Linux">
       <HintPath>..\ThirdParty\MediaBrowser.IsoMounting.Linux\MediaBrowser.IsoMounting.Linux.dll</HintPath>

+ 25 - 2
MediaBrowser.Server.Mono/Program.cs

@@ -10,18 +10,21 @@ using System.Linq;
 using System.Net;
 using System.Net.Security;
 using System.Reflection;
-using System.Security.Cryptography.X509Certificates;
 using System.Text.RegularExpressions;
 using System.Threading.Tasks;
 using Emby.Common.Implementations.EnvironmentInfo;
 using Emby.Common.Implementations.IO;
 using Emby.Common.Implementations.Logging;
+using Emby.Common.Implementations.Networking;
+using Emby.Common.Implementations.Security;
 using Emby.Server.Core;
 using Emby.Server.Implementations.IO;
 using MediaBrowser.Model.System;
+using MediaBrowser.Server.Startup.Common.IO;
 using Mono.Unix.Native;
 using NLog;
 using ILogger = MediaBrowser.Model.Logging.ILogger;
+using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
 
 namespace MediaBrowser.Server.Mono
 {
@@ -90,7 +93,22 @@ namespace MediaBrowser.Server.Mono
 
             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"))
             {
@@ -115,6 +133,11 @@ namespace MediaBrowser.Server.Mono
             Task.WaitAll(task);
         }
 
+        private static void GenerateCertificate(string certPath, string certHost)
+        {
+            CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, _logger);
+        }
+
         private static MonoEnvironmentInfo GetEnvironmentInfo()
         {
             var info = new MonoEnvironmentInfo();

+ 0 - 1
MediaBrowser.Server.Mono/packages.config

@@ -2,5 +2,4 @@
 <packages>
   <package id="Mono.Posix" version="4.0.0.0" targetFramework="net45" />
   <package id="NLog" version="4.4.0-betaV15" targetFramework="net46" />
-  <package id="Patterns.Logging" version="1.0.0.6" targetFramework="net46" />
 </packages>

+ 1 - 1
MediaBrowser.Server.Startup.Common/Cryptography/CertificateGenerator.cs

@@ -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>";
 
-        internal static void CreateSelfSignCertificatePfx(
+        public static void CreateSelfSignCertificatePfx(
             string fileName,
             string hostname,
             ILogger logger)

+ 6 - 5
MediaBrowser.Server.Startup.Common/ImageEncoderHelper.cs

@@ -1,9 +1,10 @@
-using Emby.Drawing;
+using System;
+using Emby.Drawing;
 using Emby.Drawing.Net;
 using Emby.Drawing.ImageMagick;
 using Emby.Server.Core;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
@@ -16,14 +17,14 @@ namespace MediaBrowser.Server.Startup.Common
             ILogManager logManager, 
             IFileSystem fileSystem, 
             StartupOptions startupOptions, 
-            IHttpClient httpClient,
-            IServerConfigurationManager config)
+            Func<IHttpClient> httpClient,
+            IApplicationPaths appPaths)
         {
             if (!startupOptions.ContainsOption("-enablegdi"))
             {
                 try
                 {
-                    return new ImageMagickEncoder(logManager.GetLogger("ImageMagick"), config.ApplicationPaths, httpClient, fileSystem, config);
+                    return new ImageMagickEncoder(logManager.GetLogger("ImageMagick"), appPaths, httpClient, fileSystem);
                 }
                 catch
                 {

+ 6 - 14
MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj

@@ -44,8 +44,8 @@
       <HintPath>..\packages\ini-parser.2.3.0\lib\net20\INIFileParser.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>
+    <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 Include="Microsoft.IO.RecyclableMemoryStream, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
@@ -60,14 +60,6 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath>
     </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">
       <HintPath>..\packages\ServiceStack.Text.4.5.4\lib\net45\ServiceStack.Text.dll</HintPath>
       <Private>True</Private>
@@ -76,10 +68,6 @@
       <HintPath>..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll</HintPath>
       <Private>True</Private>
     </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.Configuration" />
     <Reference Include="System.Core" />
@@ -212,6 +200,10 @@
       <Project>{21002819-c39a-4d3e-be83-2a276a77fb1f}</Project>
       <Name>RSSDP</Name>
     </ProjectReference>
+    <ProjectReference Include="..\ServiceStack\ServiceStack.csproj">
+      <Project>{680a1709-25eb-4d52-a87f-ee03ffd94baa}</Project>
+      <Name>ServiceStack</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <None Include="app.config" />

+ 1 - 3
MediaBrowser.Server.Startup.Common/packages.config

@@ -1,11 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <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="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="SimpleInjector" version="3.2.4" targetFramework="net46" />
-  <package id="SocketHttpListener.Portable" version="1.0.50" targetFramework="net46" />
 </packages>

+ 20 - 4
MediaBrowser.ServerApplication/MainStartup.cs

@@ -20,11 +20,14 @@ using System.Windows.Forms;
 using Emby.Common.Implementations.EnvironmentInfo;
 using Emby.Common.Implementations.IO;
 using Emby.Common.Implementations.Logging;
+using Emby.Common.Implementations.Networking;
+using Emby.Common.Implementations.Security;
 using Emby.Server.Core;
 using Emby.Server.Core.Browser;
 using Emby.Server.Implementations.IO;
 using ImageMagickSharp;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Server.Startup.Common.IO;
 
 namespace MediaBrowser.ServerApplication
 {
@@ -64,8 +67,8 @@ namespace MediaBrowser.ServerApplication
             return false;
         }
         /// <summary>
-         /// Defines the entry point of the application.
-         /// </summary>
+        /// Defines the entry point of the application.
+        /// </summary>
         public static void Main()
         {
             var options = new StartupOptions();
@@ -319,14 +322,22 @@ namespace MediaBrowser.ServerApplication
                 IsRunningAsService = runService
             };
 
+            var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths);
+
             _appHost = new ApplicationHost(appPaths,
                 logManager,
                 options,
                 fileSystem,
-                nativeApp, 
+                nativeApp,
                 new PowerManagement(),
                 "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>();
 
@@ -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 TaskScheduler _mainTaskScheduler;
         private static void ShowTrayIcon()

+ 0 - 4
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

@@ -78,10 +78,6 @@
       <HintPath>..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.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>
     <Reference Include="System" />
     <Reference Include="System.Configuration" />
     <Reference Include="System.Configuration.Install" />

+ 0 - 1
MediaBrowser.ServerApplication/packages.config

@@ -2,6 +2,5 @@
 <packages>
   <package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" />
   <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" />
 </packages>

+ 0 - 2
MediaBrowser.WebDashboard/packages.config

@@ -1,5 +1,3 @@
 <?xml version="1.0" encoding="utf-8"?>
 <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>

+ 359 - 0
MediaBrowser.sln

@@ -78,6 +78,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.ImageMagick",
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.Net", "Emby.Drawing.Net\Emby.Drawing.Net.csproj", "{C97A239E-A96C-4D64-A844-CCF8CC30AECB}"
 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
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -95,6 +99,11 @@ Global
 		Release|Win32 = Release|Win32
 		Release|x64 = Release|x64
 		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
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x64.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.Build.0 = 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|x64.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.Build.0 = 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|x64.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.Build.0 = 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|x64.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.Build.0 = 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|x64.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.Build.0 = 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|x64.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.Build.0 = 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|x64.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.Build.0 = 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|x64.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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x64.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.Build.0 = 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|x64.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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = 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
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 27 - 0
ServiceStack/FilterAttributeCache.cs

@@ -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 - 0
ServiceStack/Host/ActionContext.cs

@@ -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 - 0
ServiceStack/Host/ContentTypes.cs

@@ -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 - 0
ServiceStack/Host/HttpResponseStreamWrapper.cs

@@ -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 - 0
ServiceStack/Host/RestHandler.cs

@@ -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 - 0
ServiceStack/Host/RestPath.cs

@@ -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 - 0
ServiceStack/Host/ServiceController.cs

@@ -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 - 0
ServiceStack/Host/ServiceExec.cs

@@ -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 - 0
ServiceStack/Host/ServiceMetadata.cs

@@ -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 - 0
ServiceStack/HttpHandlerFactory.cs

@@ -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 - 0
ServiceStack/HttpRequestExtensions.cs

@@ -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 - 0
ServiceStack/HttpResponseExtensionsInternal.cs

@@ -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 - 0
ServiceStack/HttpResult.cs

@@ -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 - 0
ServiceStack/HttpUtils.cs

@@ -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 - 0
ServiceStack/Properties/AssemblyInfo.cs

@@ -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 - 0
ServiceStack/ReflectionExtensions.cs

@@ -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 - 0
ServiceStack/ServiceStack.csproj

@@ -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>

+ 0 - 0
Emby.Server.Implementations/Emby.Server.Implementations.nuget.targets → ServiceStack/ServiceStack.nuget.targets


+ 19 - 0
ServiceStack/ServiceStack.xproj

@@ -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 - 0
ServiceStack/ServiceStackHost.Runtime.cs

@@ -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 - 0
ServiceStack/ServiceStackHost.cs

@@ -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 - 0
ServiceStack/StringMapTypeDeserializer.cs

@@ -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 - 0
ServiceStack/UrlExtensions.cs

@@ -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 - 0
ServiceStack/packages.config

@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+</packages>

+ 1 - 1
Emby.Server.Implementations/project.json → ServiceStack/project.json

@@ -1,4 +1,4 @@
-{
+{
     "frameworks":{
         "netstandard1.6":{
            "dependencies":{

+ 17 - 0
SocketHttpListener.Portable/ByteOrder.cs

@@ -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 - 0
SocketHttpListener.Portable/CloseEventArgs.cs

@@ -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 - 0
SocketHttpListener.Portable/CloseStatusCode.cs

@@ -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 - 0
SocketHttpListener.Portable/CompressionMethod.cs

@@ -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 - 0
SocketHttpListener.Portable/ErrorEventArgs.cs

@@ -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 - 0
SocketHttpListener.Portable/Ext.cs

@@ -0,0 +1,1089 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.IO.Compression;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Services;
+using SocketHttpListener.Net;
+using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
+using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
+
+namespace SocketHttpListener
+{
+    /// <summary>
+    /// Provides a set of static methods for the websocket-sharp.
+    /// </summary>
+    public static class Ext
+    {
+        #region Private Const Fields
+
+        private const string _tspecials = "()<>@,;:\\\"/[]?={} \t";
+
+        #endregion
+
+        #region Private Methods
+
+        private static MemoryStream compress(this Stream stream)
+        {
+            var output = new MemoryStream();
+            if (stream.Length == 0)
+                return output;
+
+            stream.Position = 0;
+            using (var ds = new DeflateStream(output, CompressionMode.Compress, true))
+            {
+                stream.CopyTo(ds);
+                //ds.Close(); // "BFINAL" set to 1.
+                output.Position = 0;
+
+                return output;
+            }
+        }
+
+        private static byte[] decompress(this byte[] value)
+        {
+            if (value.Length == 0)
+                return value;
+
+            using (var input = new MemoryStream(value))
+            {
+                return input.decompressToArray();
+            }
+        }
+
+        private static MemoryStream decompress(this Stream stream)
+        {
+            var output = new MemoryStream();
+            if (stream.Length == 0)
+                return output;
+
+            stream.Position = 0;
+            using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true))
+            {
+                ds.CopyTo(output, true);
+                return output;
+            }
+        }
+
+        private static byte[] decompressToArray(this Stream stream)
+        {
+            using (var decomp = stream.decompress())
+            {
+                return decomp.ToArray();
+            }
+        }
+
+        private static byte[] readBytes(this Stream stream, byte[] buffer, int offset, int length)
+        {
+            var len = stream.Read(buffer, offset, length);
+            if (len < 1)
+                return buffer.SubArray(0, offset);
+
+            var tmp = 0;
+            while (len < length)
+            {
+                tmp = stream.Read(buffer, offset + len, length - len);
+                if (tmp < 1)
+                    break;
+
+                len += tmp;
+            }
+
+            return len < length
+                   ? buffer.SubArray(0, offset + len)
+                   : buffer;
+        }
+
+        private static bool readBytes(
+          this Stream stream, byte[] buffer, int offset, int length, Stream dest)
+        {
+            var bytes = stream.readBytes(buffer, offset, length);
+            var len = bytes.Length;
+            dest.Write(bytes, 0, len);
+
+            return len == offset + length;
+        }
+
+        #endregion
+
+        #region Internal Methods
+
+        internal static byte[] Append(this ushort code, string reason)
+        {
+            using (var buffer = new MemoryStream())
+            {
+                var tmp = code.ToByteArrayInternally(ByteOrder.Big);
+                buffer.Write(tmp, 0, 2);
+                if (reason != null && reason.Length > 0)
+                {
+                    tmp = Encoding.UTF8.GetBytes(reason);
+                    buffer.Write(tmp, 0, tmp.Length);
+                }
+
+                return buffer.ToArray();
+            }
+        }
+
+        internal static string CheckIfClosable(this WebSocketState state)
+        {
+            return state == WebSocketState.Closing
+                   ? "While closing the WebSocket connection."
+                   : state == WebSocketState.Closed
+                     ? "The WebSocket connection has already been closed."
+                     : null;
+        }
+
+        internal static string CheckIfOpen(this WebSocketState state)
+        {
+            return state == WebSocketState.Connecting
+                   ? "A WebSocket connection isn't established."
+                   : state == WebSocketState.Closing
+                     ? "While closing the WebSocket connection."
+                     : state == WebSocketState.Closed
+                       ? "The WebSocket connection has already been closed."
+                       : null;
+        }
+
+        internal static string CheckIfValidControlData(this byte[] data, string paramName)
+        {
+            return data.Length > 125
+                   ? String.Format("'{0}' length must be less.", paramName)
+                   : null;
+        }
+
+        internal static string CheckIfValidSendData(this byte[] data)
+        {
+            return data == null
+                   ? "'data' must not be null."
+                   : null;
+        }
+
+        internal static string CheckIfValidSendData(this string data)
+        {
+            return data == null
+                   ? "'data' must not be null."
+                   : null;
+        }
+
+        internal static void Close(this HttpListenerResponse response, HttpStatusCode code)
+        {
+            response.StatusCode = (int)code;
+            response.OutputStream.Dispose();
+        }
+
+        internal static Stream Compress(this Stream stream, CompressionMethod method)
+        {
+            return method == CompressionMethod.Deflate
+                   ? stream.compress()
+                   : stream;
+        }
+
+        internal static bool Contains<T>(this IEnumerable<T> source, Func<T, bool> condition)
+        {
+            foreach (T elm in source)
+                if (condition(elm))
+                    return true;
+
+            return false;
+        }
+
+        internal static void CopyTo(this Stream src, Stream dest, bool setDefaultPosition)
+        {
+            var readLen = 0;
+            var bufferLen = 256;
+            var buffer = new byte[bufferLen];
+            while ((readLen = src.Read(buffer, 0, bufferLen)) > 0)
+            {
+                dest.Write(buffer, 0, readLen);
+            }
+
+            if (setDefaultPosition)
+                dest.Position = 0;
+        }
+
+        internal static byte[] Decompress(this byte[] value, CompressionMethod method)
+        {
+            return method == CompressionMethod.Deflate
+                   ? value.decompress()
+                   : value;
+        }
+
+        internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method)
+        {
+            return method == CompressionMethod.Deflate
+                   ? stream.decompressToArray()
+                   : stream.ToByteArray();
+        }
+
+        /// <summary>
+        /// Determines whether the specified <see cref="int"/> equals the specified <see cref="char"/>,
+        /// and invokes the specified Action&lt;int&gt; delegate at the same time.
+        /// </summary>
+        /// <returns>
+        /// <c>true</c> if <paramref name="value"/> equals <paramref name="c"/>;
+        /// otherwise, <c>false</c>.
+        /// </returns>
+        /// <param name="value">
+        /// An <see cref="int"/> to compare.
+        /// </param>
+        /// <param name="c">
+        /// A <see cref="char"/> to compare.
+        /// </param>
+        /// <param name="action">
+        /// An Action&lt;int&gt; delegate that references the method(s) called at
+        /// the same time as comparing. An <see cref="int"/> parameter to pass to
+        /// the method(s) is <paramref name="value"/>.
+        /// </param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// <paramref name="value"/> isn't between 0 and 255.
+        /// </exception>
+        internal static bool EqualsWith(this int value, char c, Action<int> action)
+        {
+            if (value < 0 || value > 255)
+                throw new ArgumentOutOfRangeException("value");
+
+            action(value);
+            return value == c - 0;
+        }
+
+        internal static string GetMessage(this CloseStatusCode code)
+        {
+            return code == CloseStatusCode.ProtocolError
+                   ? "A WebSocket protocol error has occurred."
+                   : code == CloseStatusCode.IncorrectData
+                     ? "An incorrect data has been received."
+                     : code == CloseStatusCode.Abnormal
+                       ? "An exception has occurred."
+                       : code == CloseStatusCode.InconsistentData
+                         ? "An inconsistent data has been received."
+                         : code == CloseStatusCode.PolicyViolation
+                           ? "A policy violation has occurred."
+                           : code == CloseStatusCode.TooBig
+                             ? "A too big data has been received."
+                             : code == CloseStatusCode.IgnoreExtension
+                               ? "WebSocket client did not receive expected extension(s)."
+                               : code == CloseStatusCode.ServerError
+                                 ? "WebSocket server got an internal error."
+                                 : code == CloseStatusCode.TlsHandshakeFailure
+                                   ? "An error has occurred while handshaking."
+                                   : String.Empty;
+        }
+
+        internal static string GetNameInternal(this string nameAndValue, string separator)
+        {
+            var i = nameAndValue.IndexOf(separator);
+            return i > 0
+                   ? nameAndValue.Substring(0, i).Trim()
+                   : null;
+        }
+
+        internal static string GetValueInternal(this string nameAndValue, string separator)
+        {
+            var i = nameAndValue.IndexOf(separator);
+            return i >= 0 && i < nameAndValue.Length - 1
+                   ? nameAndValue.Substring(i + 1).Trim()
+                   : null;
+        }
+
+        internal static bool IsCompressionExtension(this string value, CompressionMethod method)
+        {
+            return value.StartsWith(method.ToExtensionString());
+        }
+
+        internal static bool IsPortNumber(this int value)
+        {
+            return value > 0 && value < 65536;
+        }
+
+        internal static bool IsReserved(this ushort code)
+        {
+            return code == (ushort)CloseStatusCode.Undefined ||
+                   code == (ushort)CloseStatusCode.NoStatusCode ||
+                   code == (ushort)CloseStatusCode.Abnormal ||
+                   code == (ushort)CloseStatusCode.TlsHandshakeFailure;
+        }
+
+        internal static bool IsReserved(this CloseStatusCode code)
+        {
+            return code == CloseStatusCode.Undefined ||
+                   code == CloseStatusCode.NoStatusCode ||
+                   code == CloseStatusCode.Abnormal ||
+                   code == CloseStatusCode.TlsHandshakeFailure;
+        }
+
+        internal static bool IsText(this string value)
+        {
+            var len = value.Length;
+            for (var i = 0; i < len; i++)
+            {
+                char c = value[i];
+                if (c < 0x20 && !"\r\n\t".Contains(c))
+                    return false;
+
+                if (c == 0x7f)
+                    return false;
+
+                if (c == '\n' && ++i < len)
+                {
+                    c = value[i];
+                    if (!" \t".Contains(c))
+                        return false;
+                }
+            }
+
+            return true;
+        }
+
+        internal static bool IsToken(this string value)
+        {
+            foreach (char c in value)
+                if (c < 0x20 || c >= 0x7f || _tspecials.Contains(c))
+                    return false;
+
+            return true;
+        }
+
+        internal static string Quote(this string value)
+        {
+            return value.IsToken()
+                   ? value
+                   : String.Format("\"{0}\"", value.Replace("\"", "\\\""));
+        }
+
+        internal static byte[] ReadBytes(this Stream stream, int length)
+        {
+            return stream.readBytes(new byte[length], 0, length);
+        }
+
+        internal static byte[] ReadBytes(this Stream stream, long length, int bufferLength)
+        {
+            using (var result = new MemoryStream())
+            {
+                var count = length / bufferLength;
+                var rem = (int)(length % bufferLength);
+
+                var buffer = new byte[bufferLength];
+                var end = false;
+                for (long i = 0; i < count; i++)
+                {
+                    if (!stream.readBytes(buffer, 0, bufferLength, result))
+                    {
+                        end = true;
+                        break;
+                    }
+                }
+
+                if (!end && rem > 0)
+                    stream.readBytes(new byte[rem], 0, rem, result);
+
+                return result.ToArray();
+            }
+        }
+
+        internal static async Task<byte[]> ReadBytesAsync(this Stream stream, int length)
+        {
+            var buffer = new byte[length];
+
+            var len = await stream.ReadAsync(buffer, 0, length).ConfigureAwait(false);
+            var bytes = len < 1
+                ? new byte[0]
+                : len < length
+                  ? stream.readBytes(buffer, len, length - len)
+                  : buffer;
+
+            return bytes;
+        }
+
+        internal static string RemovePrefix(this string value, params string[] prefixes)
+        {
+            var i = 0;
+            foreach (var prefix in prefixes)
+            {
+                if (value.StartsWith(prefix))
+                {
+                    i = prefix.Length;
+                    break;
+                }
+            }
+
+            return i > 0
+                   ? value.Substring(i)
+                   : value;
+        }
+
+        internal static T[] Reverse<T>(this T[] array)
+        {
+            var len = array.Length;
+            T[] reverse = new T[len];
+
+            var end = len - 1;
+            for (var i = 0; i <= end; i++)
+                reverse[i] = array[end - i];
+
+            return reverse;
+        }
+
+        internal static IEnumerable<string> SplitHeaderValue(
+          this string value, params char[] separator)
+        {
+            var len = value.Length;
+            var separators = new string(separator);
+
+            var buffer = new StringBuilder(32);
+            var quoted = false;
+            var escaped = false;
+
+            char c;
+            for (var i = 0; i < len; i++)
+            {
+                c = value[i];
+                if (c == '"')
+                {
+                    if (escaped)
+                        escaped = !escaped;
+                    else
+                        quoted = !quoted;
+                }
+                else if (c == '\\')
+                {
+                    if (i < len - 1 && value[i + 1] == '"')
+                        escaped = true;
+                }
+                else if (separators.Contains(c))
+                {
+                    if (!quoted)
+                    {
+                        yield return buffer.ToString();
+                        buffer.Length = 0;
+
+                        continue;
+                    }
+                }
+                else {
+                }
+
+                buffer.Append(c);
+            }
+
+            if (buffer.Length > 0)
+                yield return buffer.ToString();
+        }
+
+        internal static byte[] ToByteArray(this Stream stream)
+        {
+            using (var output = new MemoryStream())
+            {
+                stream.Position = 0;
+                stream.CopyTo(output);
+
+                return output.ToArray();
+            }
+        }
+
+        internal static byte[] ToByteArrayInternally(this ushort value, ByteOrder order)
+        {
+            var bytes = BitConverter.GetBytes(value);
+            if (!order.IsHostOrder())
+                Array.Reverse(bytes);
+
+            return bytes;
+        }
+
+        internal static byte[] ToByteArrayInternally(this ulong value, ByteOrder order)
+        {
+            var bytes = BitConverter.GetBytes(value);
+            if (!order.IsHostOrder())
+                Array.Reverse(bytes);
+
+            return bytes;
+        }
+
+        internal static string ToExtensionString(
+          this CompressionMethod method, params string[] parameters)
+        {
+            if (method == CompressionMethod.None)
+                return String.Empty;
+
+            var m = String.Format("permessage-{0}", method.ToString().ToLower());
+            if (parameters == null || parameters.Length == 0)
+                return m;
+
+            return String.Format("{0}; {1}", m, parameters.ToString("; "));
+        }
+
+        internal static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
+        {
+            return new List<TSource>(source);
+        }
+
+        internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder)
+        {
+            return BitConverter.ToUInt16(src.ToHostOrder(srcOrder), 0);
+        }
+
+        internal static ulong ToUInt64(this byte[] src, ByteOrder srcOrder)
+        {
+            return BitConverter.ToUInt64(src.ToHostOrder(srcOrder), 0);
+        }
+
+        internal static string TrimEndSlash(this string value)
+        {
+            value = value.TrimEnd('/');
+            return value.Length > 0
+                   ? value
+                   : "/";
+        }
+
+        internal static string Unquote(this string value)
+        {
+            var start = value.IndexOf('\"');
+            var end = value.LastIndexOf('\"');
+            if (start < end)
+                value = value.Substring(start + 1, end - start - 1).Replace("\\\"", "\"");
+
+            return value.Trim();
+        }
+
+        internal static void WriteBytes(this Stream stream, byte[] value)
+        {
+            using (var src = new MemoryStream(value))
+            {
+                src.CopyTo(stream);
+            }
+        }
+
+        #endregion
+
+        #region Public Methods
+
+        /// <summary>
+        /// Determines whether the specified <see cref="string"/> contains any of characters
+        /// in the specified array of <see cref="char"/>.
+        /// </summary>
+        /// <returns>
+        /// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;
+        /// otherwise, <c>false</c>.
+        /// </returns>
+        /// <param name="value">
+        /// A <see cref="string"/> to test.
+        /// </param>
+        /// <param name="chars">
+        /// An array of <see cref="char"/> that contains characters to find.
+        /// </param>
+        public static bool Contains(this string value, params char[] chars)
+        {
+            return chars == null || chars.Length == 0
+                   ? true
+                   : value == null || value.Length == 0
+                     ? false
+                     : value.IndexOfAny(chars) != -1;
+        }
+
+        /// <summary>
+        /// Determines whether the specified <see cref="QueryParamCollection"/> contains the entry
+        /// with the specified <paramref name="name"/>.
+        /// </summary>
+        /// <returns>
+        /// <c>true</c> if <paramref name="collection"/> contains the entry
+        /// with <paramref name="name"/>; otherwise, <c>false</c>.
+        /// </returns>
+        /// <param name="collection">
+        /// A <see cref="QueryParamCollection"/> to test.
+        /// </param>
+        /// <param name="name">
+        /// A <see cref="string"/> that represents the key of the entry to find.
+        /// </param>
+        public static bool Contains(this QueryParamCollection collection, string name)
+        {
+            return collection == null || collection.Count == 0
+                   ? false
+                   : collection[name] != null;
+        }
+
+        /// <summary>
+        /// Determines whether the specified <see cref="QueryParamCollection"/> contains the entry
+        /// with the specified both <paramref name="name"/> and <paramref name="value"/>.
+        /// </summary>
+        /// <returns>
+        /// <c>true</c> if <paramref name="collection"/> contains the entry
+        /// with both <paramref name="name"/> and <paramref name="value"/>;
+        /// otherwise, <c>false</c>.
+        /// </returns>
+        /// <param name="collection">
+        /// A <see cref="QueryParamCollection"/> to test.
+        /// </param>
+        /// <param name="name">
+        /// A <see cref="string"/> that represents the key of the entry to find.
+        /// </param>
+        /// <param name="value">
+        /// A <see cref="string"/> that represents the value of the entry to find.
+        /// </param>
+        public static bool Contains(this QueryParamCollection collection, string name, string value)
+        {
+            if (collection == null || collection.Count == 0)
+                return false;
+
+            var values = collection[name];
+            if (values == null)
+                return false;
+
+            foreach (var v in values.Split(','))
+                if (v.Trim().Equals(value, StringComparison.OrdinalIgnoreCase))
+                    return true;
+
+            return false;
+        }
+
+        /// <summary>
+        /// Emits the specified <see cref="EventHandler"/> delegate if it isn't <see langword="null"/>.
+        /// </summary>
+        /// <param name="eventHandler">
+        /// A <see cref="EventHandler"/> to emit.
+        /// </param>
+        /// <param name="sender">
+        /// An <see cref="object"/> from which emits this <paramref name="eventHandler"/>.
+        /// </param>
+        /// <param name="e">
+        /// A <see cref="EventArgs"/> that contains no event data.
+        /// </param>
+        public static void Emit(this EventHandler eventHandler, object sender, EventArgs e)
+        {
+            if (eventHandler != null)
+                eventHandler(sender, e);
+        }
+
+        /// <summary>
+        /// Emits the specified <c>EventHandler&lt;TEventArgs&gt;</c> delegate
+        /// if it isn't <see langword="null"/>.
+        /// </summary>
+        /// <param name="eventHandler">
+        /// An <c>EventHandler&lt;TEventArgs&gt;</c> to emit.
+        /// </param>
+        /// <param name="sender">
+        /// An <see cref="object"/> from which emits this <paramref name="eventHandler"/>.
+        /// </param>
+        /// <param name="e">
+        /// A <c>TEventArgs</c> that represents the event data.
+        /// </param>
+        /// <typeparam name="TEventArgs">
+        /// The type of the event data generated by the event.
+        /// </typeparam>
+        public static void Emit<TEventArgs>(
+          this EventHandler<TEventArgs> eventHandler, object sender, TEventArgs e)
+          where TEventArgs : EventArgs
+        {
+            if (eventHandler != null)
+                eventHandler(sender, e);
+        }
+
+        /// <summary>
+        /// Gets the collection of the HTTP cookies from the specified HTTP <paramref name="headers"/>.
+        /// </summary>
+        /// <returns>
+        /// A <see cref="CookieCollection"/> that receives a collection of the HTTP cookies.
+        /// </returns>
+        /// <param name="headers">
+        /// A <see cref="QueryParamCollection"/> that contains a collection of the HTTP headers.
+        /// </param>
+        /// <param name="response">
+        /// <c>true</c> if <paramref name="headers"/> is a collection of the response headers;
+        /// otherwise, <c>false</c>.
+        /// </param>
+        public static CookieCollection GetCookies(this QueryParamCollection headers, bool response)
+        {
+            var name = response ? "Set-Cookie" : "Cookie";
+            return headers == null || !headers.Contains(name)
+                   ? new CookieCollection()
+                   : CookieHelper.Parse(headers[name], response);
+        }
+
+        /// <summary>
+        /// Gets the description of the specified HTTP status <paramref name="code"/>.
+        /// </summary>
+        /// <returns>
+        /// A <see cref="string"/> that represents the description of the HTTP status code.
+        /// </returns>
+        /// <param name="code">
+        /// One of <see cref="HttpStatusCode"/> enum values, indicates the HTTP status codes.
+        /// </param>
+        public static string GetDescription(this HttpStatusCode code)
+        {
+            return ((int)code).GetStatusDescription();
+        }
+
+        /// <summary>
+        /// Gets the name from the specified <see cref="string"/> that contains a pair of name and
+        /// value separated by a separator string.
+        /// </summary>
+        /// <returns>
+        /// A <see cref="string"/> that represents the name if any; otherwise, <c>null</c>.
+        /// </returns>
+        /// <param name="nameAndValue">
+        /// A <see cref="string"/> that contains a pair of name and value separated by a separator
+        /// string.
+        /// </param>
+        /// <param name="separator">
+        /// A <see cref="string"/> that represents a separator string.
+        /// </param>
+        public static string GetName(this string nameAndValue, string separator)
+        {
+            return (nameAndValue != null && nameAndValue.Length > 0) &&
+                   (separator != null && separator.Length > 0)
+                   ? nameAndValue.GetNameInternal(separator)
+                   : null;
+        }
+
+        /// <summary>
+        /// Gets the name and value from the specified <see cref="string"/> that contains a pair of
+        /// name and value separated by a separator string.
+        /// </summary>
+        /// <returns>
+        /// A <c>KeyValuePair&lt;string, string&gt;</c> that represents the name and value if any.
+        /// </returns>
+        /// <param name="nameAndValue">
+        /// A <see cref="string"/> that contains a pair of name and value separated by a separator
+        /// string.
+        /// </param>
+        /// <param name="separator">
+        /// A <see cref="string"/> that represents a separator string.
+        /// </param>
+        public static KeyValuePair<string, string> GetNameAndValue(
+          this string nameAndValue, string separator)
+        {
+            var name = nameAndValue.GetName(separator);
+            var value = nameAndValue.GetValue(separator);
+            return name != null
+                   ? new KeyValuePair<string, string>(name, value)
+                   : new KeyValuePair<string, string>(null, null);
+        }
+
+        /// <summary>
+        /// Gets the description of the specified HTTP status <paramref name="code"/>.
+        /// </summary>
+        /// <returns>
+        /// A <see cref="string"/> that represents the description of the HTTP status code.
+        /// </returns>
+        /// <param name="code">
+        /// An <see cref="int"/> that represents the HTTP status code.
+        /// </param>
+        public static string GetStatusDescription(this 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 String.Empty;
+        }
+
+        /// <summary>
+        /// Gets the value from the specified <see cref="string"/> that contains a pair of name and
+        /// value separated by a separator string.
+        /// </summary>
+        /// <returns>
+        /// A <see cref="string"/> that represents the value if any; otherwise, <c>null</c>.
+        /// </returns>
+        /// <param name="nameAndValue">
+        /// A <see cref="string"/> that contains a pair of name and value separated by a separator
+        /// string.
+        /// </param>
+        /// <param name="separator">
+        /// A <see cref="string"/> that represents a separator string.
+        /// </param>
+        public static string GetValue(this string nameAndValue, string separator)
+        {
+            return (nameAndValue != null && nameAndValue.Length > 0) &&
+                   (separator != null && separator.Length > 0)
+                   ? nameAndValue.GetValueInternal(separator)
+                   : null;
+        }
+
+        /// <summary>
+        /// Determines whether the specified <see cref="ByteOrder"/> is host
+        /// (this computer architecture) byte order.
+        /// </summary>
+        /// <returns>
+        /// <c>true</c> if <paramref name="order"/> is host byte order;
+        /// otherwise, <c>false</c>.
+        /// </returns>
+        /// <param name="order">
+        /// One of the <see cref="ByteOrder"/> enum values, to test.
+        /// </param>
+        public static bool IsHostOrder(this ByteOrder order)
+        {
+            // true : !(true ^ true)  or !(false ^ false)
+            // false: !(true ^ false) or !(false ^ true)
+            return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little));
+        }
+
+        /// <summary>
+        /// Determines whether the specified <see cref="string"/> is a predefined scheme.
+        /// </summary>
+        /// <returns>
+        /// <c>true</c> if <paramref name="value"/> is a predefined scheme; otherwise, <c>false</c>.
+        /// </returns>
+        /// <param name="value">
+        /// A <see cref="string"/> to test.
+        /// </param>
+        public static bool IsPredefinedScheme(this string value)
+        {
+            if (value == null || value.Length < 2)
+                return false;
+
+            var c = value[0];
+            if (c == 'h')
+                return value == "http" || value == "https";
+
+            if (c == 'w')
+                return value == "ws" || value == "wss";
+
+            if (c == 'f')
+                return value == "file" || value == "ftp";
+
+            if (c == 'n')
+            {
+                c = value[1];
+                return c == 'e'
+                       ? value == "news" || value == "net.pipe" || value == "net.tcp"
+                       : value == "nntp";
+            }
+
+            return (c == 'g' && value == "gopher") || (c == 'm' && value == "mailto");
+        }
+
+        /// <summary>
+        /// Determines whether the specified <see cref="string"/> is a URI string.
+        /// </summary>
+        /// <returns>
+        /// <c>true</c> if <paramref name="value"/> may be a URI string; otherwise, <c>false</c>.
+        /// </returns>
+        /// <param name="value">
+        /// A <see cref="string"/> to test.
+        /// </param>
+        public static bool MaybeUri(this string value)
+        {
+            if (value == null || value.Length == 0)
+                return false;
+
+            var i = value.IndexOf(':');
+            if (i == -1)
+                return false;
+
+            if (i >= 10)
+                return false;
+
+            return value.Substring(0, i).IsPredefinedScheme();
+        }
+
+        /// <summary>
+        /// Retrieves a sub-array from the specified <paramref name="array"/>.
+        /// A sub-array starts at the specified element position.
+        /// </summary>
+        /// <returns>
+        /// An array of T that receives a sub-array, or an empty array of T if any problems
+        /// with the parameters.
+        /// </returns>
+        /// <param name="array">
+        /// An array of T that contains the data to retrieve a sub-array.
+        /// </param>
+        /// <param name="startIndex">
+        /// An <see cref="int"/> that contains the zero-based starting position of a sub-array
+        /// in <paramref name="array"/>.
+        /// </param>
+        /// <param name="length">
+        /// An <see cref="int"/> that contains the number of elements to retrieve a sub-array.
+        /// </param>
+        /// <typeparam name="T">
+        /// The type of elements in the <paramref name="array"/>.
+        /// </typeparam>
+        public static T[] SubArray<T>(this T[] array, int startIndex, int length)
+        {
+            if (array == null || array.Length == 0)
+                return new T[0];
+
+            if (startIndex < 0 || length <= 0)
+                return new T[0];
+
+            if (startIndex + length > array.Length)
+                return new T[0];
+
+            if (startIndex == 0 && array.Length == length)
+                return array;
+
+            T[] subArray = new T[length];
+            Array.Copy(array, startIndex, subArray, 0, length);
+
+            return subArray;
+        }
+
+        /// <summary>
+        /// Converts the order of the specified array of <see cref="byte"/> to the host byte order.
+        /// </summary>
+        /// <returns>
+        /// An array of <see cref="byte"/> converted from <paramref name="src"/>.
+        /// </returns>
+        /// <param name="src">
+        /// An array of <see cref="byte"/> to convert.
+        /// </param>
+        /// <param name="srcOrder">
+        /// One of the <see cref="ByteOrder"/> enum values, indicates the byte order of
+        /// <paramref name="src"/>.
+        /// </param>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="src"/> is <see langword="null"/>.
+        /// </exception>
+        public static byte[] ToHostOrder(this byte[] src, ByteOrder srcOrder)
+        {
+            if (src == null)
+                throw new ArgumentNullException("src");
+
+            return src.Length > 1 && !srcOrder.IsHostOrder()
+                   ? src.Reverse()
+                   : src;
+        }
+
+        /// <summary>
+        /// Converts the specified <paramref name="array"/> to a <see cref="string"/> that
+        /// concatenates the each element of <paramref name="array"/> across the specified
+        /// <paramref name="separator"/>.
+        /// </summary>
+        /// <returns>
+        /// A <see cref="string"/> converted from <paramref name="array"/>,
+        /// or <see cref="String.Empty"/> if <paramref name="array"/> is empty.
+        /// </returns>
+        /// <param name="array">
+        /// An array of T to convert.
+        /// </param>
+        /// <param name="separator">
+        /// A <see cref="string"/> that represents the separator string.
+        /// </param>
+        /// <typeparam name="T">
+        /// The type of elements in <paramref name="array"/>.
+        /// </typeparam>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="array"/> is <see langword="null"/>.
+        /// </exception>
+        public static string ToString<T>(this T[] array, string separator)
+        {
+            if (array == null)
+                throw new ArgumentNullException("array");
+
+            var len = array.Length;
+            if (len == 0)
+                return String.Empty;
+
+            if (separator == null)
+                separator = String.Empty;
+
+            var buff = new StringBuilder(64);
+            (len - 1).Times(i => buff.AppendFormat("{0}{1}", array[i].ToString(), separator));
+
+            buff.Append(array[len - 1].ToString());
+            return buff.ToString();
+        }
+
+        /// <summary>
+        /// Executes the specified <c>Action&lt;int&gt;</c> delegate <paramref name="n"/> times.
+        /// </summary>
+        /// <param name="n">
+        /// An <see cref="int"/> is the number of times to execute.
+        /// </param>
+        /// <param name="action">
+        /// An <c>Action&lt;int&gt;</c> delegate that references the method(s) to execute.
+        /// An <see cref="int"/> parameter to pass to the method(s) is the zero-based count of
+        /// iteration.
+        /// </param>
+        public static void Times(this int n, Action<int> action)
+        {
+            if (n > 0 && action != null)
+                for (int i = 0; i < n; i++)
+                    action(i);
+        }
+
+        /// <summary>
+        /// Converts the specified <see cref="string"/> to a <see cref="Uri"/>.
+        /// </summary>
+        /// <returns>
+        /// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or <see langword="null"/>
+        /// if <paramref name="uriString"/> isn't successfully converted.
+        /// </returns>
+        /// <param name="uriString">
+        /// A <see cref="string"/> to convert.
+        /// </param>
+        public static Uri ToUri(this string uriString)
+        {
+            Uri res;
+            return Uri.TryCreate(
+                     uriString, uriString.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out res)
+                   ? res
+                   : null;
+        }
+
+        /// <summary>
+        /// URL-decodes the specified <see cref="string"/>.
+        /// </summary>
+        /// <returns>
+        /// A <see cref="string"/> that receives the decoded string, or the <paramref name="value"/>
+        /// if it's <see langword="null"/> or empty.
+        /// </returns>
+        /// <param name="value">
+        /// A <see cref="string"/> to decode.
+        /// </param>
+        public static string UrlDecode(this string value)
+        {
+            return value == null || value.Length == 0
+                   ? value
+                   : WebUtility.UrlDecode(value);
+        }
+
+        #endregion
+    }
+}

+ 8 - 0
SocketHttpListener.Portable/Fin.cs

@@ -0,0 +1,8 @@
+namespace SocketHttpListener
+{
+  internal enum Fin : byte
+  {
+    More = 0x0,
+    Final = 0x1
+  }
+}

+ 104 - 0
SocketHttpListener.Portable/HttpBase.cs

@@ -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 - 0
SocketHttpListener.Portable/HttpResponse.cs

@@ -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 - 0
SocketHttpListener.Portable/Mask.cs

@@ -0,0 +1,8 @@
+namespace SocketHttpListener
+{
+  internal enum Mask : byte
+  {
+    Unmask = 0x0,
+    Mask = 0x1
+  }
+}

+ 96 - 0
SocketHttpListener.Portable/MessageEventArgs.cs

@@ -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
+  }
+}

+ 6 - 0
SocketHttpListener.Portable/Net/AuthenticationSchemeSelector.cs

@@ -0,0 +1,6 @@
+using System.Net;
+
+namespace SocketHttpListener.Net
+{
+    public delegate AuthenticationSchemes AuthenticationSchemeSelector(HttpListenerRequest httpRequest);
+}

+ 371 - 0
SocketHttpListener.Portable/Net/ChunkStream.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/ChunkedInputStream.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/CookieHelper.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/EndPointListener.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/EndPointManager.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/HttpConnection.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/HttpListener.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/HttpListenerBasicIdentity.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/HttpListenerContext.cs

@@ -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;
+        }
+    }
+}

+ 97 - 0
SocketHttpListener.Portable/Net/HttpListenerPrefixCollection.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/HttpListenerRequest.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/HttpListenerResponse.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/HttpStatusCode.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/HttpStreamAsyncResult.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/HttpVersion.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/ListenerPrefix.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/RequestStream.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/ResponseStream.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/WebHeaderCollection.cs

@@ -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;
+        }
+    }
+}

+ 347 - 0
SocketHttpListener.Portable/Net/WebSockets/HttpListenerWebSocketContext.cs

@@ -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 - 0
SocketHttpListener.Portable/Net/WebSockets/WebSocketContext.cs

@@ -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 - 0
SocketHttpListener.Portable/Opcode.cs

@@ -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 - 0
SocketHttpListener.Portable/PayloadData.cs

@@ -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
+  }
+}

+ 17 - 0
SocketHttpListener.Portable/Primitives/HttpListenerException.cs

@@ -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 - 0
SocketHttpListener.Portable/Primitives/ICertificate.cs

@@ -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 - 0
SocketHttpListener.Portable/Primitives/IStreamFactory.cs

@@ -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 - 0
SocketHttpListener.Portable/Primitives/ITextEncoding.cs

@@ -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 - 0
SocketHttpListener.Portable/Properties/AssemblyInfo.cs

@@ -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 - 0
SocketHttpListener.Portable/Rsv.cs

@@ -0,0 +1,8 @@
+namespace SocketHttpListener
+{
+  internal enum Rsv : byte
+  {
+    Off = 0x0,
+    On = 0x1
+  }
+}

+ 109 - 0
SocketHttpListener.Portable/SocketHttpListener.Portable.csproj

@@ -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>

+ 6 - 0
SocketHttpListener.Portable/SocketHttpListener.Portable.nuget.targets

@@ -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 - 0
SocketHttpListener.Portable/WebSocket.cs

@@ -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&lt;bool&gt; 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&lt;bool&gt; 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 - 0
SocketHttpListener.Portable/WebSocketException.cs

@@ -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 - 0
SocketHttpListener.Portable/WebSocketFrame.cs

@@ -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 - 0
SocketHttpListener.Portable/WebSocketState.cs

@@ -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 - 0
SocketHttpListener.Portable/packages.config

@@ -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 - 0
SocketHttpListener.Portable/project.json

@@ -0,0 +1,17 @@
+{
+    "frameworks":{
+        "netstandard1.6":{
+           "dependencies":{
+                "NETStandard.Library":"1.6.0",
+            }
+        },
+        ".NETPortable,Version=v4.5,Profile=Profile7":{
+            "buildOptions": {
+                "define": [  ]
+            },
+            "frameworkAssemblies":{
+                
+            }
+        }
+    }
+}