aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model/Services
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Model/Services')
-rw-r--r--MediaBrowser.Model/Services/ApiMemberAttribute.cs61
-rw-r--r--MediaBrowser.Model/Services/HttpUtility.cs923
-rw-r--r--MediaBrowser.Model/Services/IAsyncStreamWriter.cs11
-rw-r--r--MediaBrowser.Model/Services/IHasHeaders.cs9
-rw-r--r--MediaBrowser.Model/Services/IHasRequestFilter.cs21
-rw-r--r--MediaBrowser.Model/Services/IHttpRequest.cs45
-rw-r--r--MediaBrowser.Model/Services/IHttpResponse.cs24
-rw-r--r--MediaBrowser.Model/Services/IHttpResult.cs36
-rw-r--r--MediaBrowser.Model/Services/IRequest.cs159
-rw-r--r--MediaBrowser.Model/Services/IRequiresRequestStream.cs12
-rw-r--r--MediaBrowser.Model/Services/IService.cs12
-rw-r--r--MediaBrowser.Model/Services/IStreamWriter.cs9
-rw-r--r--MediaBrowser.Model/Services/QueryParamCollection.cs229
-rw-r--r--MediaBrowser.Model/Services/RouteAttribute.cs148
14 files changed, 1699 insertions, 0 deletions
diff --git a/MediaBrowser.Model/Services/ApiMemberAttribute.cs b/MediaBrowser.Model/Services/ApiMemberAttribute.cs
new file mode 100644
index 000000000..4a2831775
--- /dev/null
+++ b/MediaBrowser.Model/Services/ApiMemberAttribute.cs
@@ -0,0 +1,61 @@
+using System;
+
+namespace MediaBrowser.Model.Services
+{
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
+ public class ApiMemberAttribute : Attribute
+ {
+ /// <summary>
+ /// Gets or sets verb to which applies attribute. By default applies to all verbs.
+ /// </summary>
+ public string Verb { get; set; }
+
+ /// <summary>
+ /// Gets or sets parameter type: It can be only one of the following: path, query, body, form, or header.
+ /// </summary>
+ public string ParameterType { get; set; }
+
+ /// <summary>
+ /// Gets or sets unique name for the parameter. Each name must be unique, even if they are associated with different paramType values.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Other notes on the name field:
+ /// If paramType is body, the name is used only for UI and codegeneration.
+ /// If paramType is path, the name field must correspond to the associated path segment from the path field in the api object.
+ /// If paramType is query, the name field corresponds to the query param name.
+ /// </para>
+ /// </remarks>
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the human-readable description for the parameter.
+ /// </summary>
+ public string Description { get; set; }
+
+ /// <summary>
+ /// For path, query, and header paramTypes, this field must be a primitive. For body, this can be a complex or container datatype.
+ /// </summary>
+ public string DataType { get; set; }
+
+ /// <summary>
+ /// For path, this is always true. Otherwise, this field tells the client whether or not the field must be supplied.
+ /// </summary>
+ public bool IsRequired { get; set; }
+
+ /// <summary>
+ /// For query params, this specifies that a comma-separated list of values can be passed to the API. For path and body types, this field cannot be true.
+ /// </summary>
+ public bool AllowMultiple { get; set; }
+
+ /// <summary>
+ /// Gets or sets route to which applies attribute, matches using StartsWith. By default applies to all routes.
+ /// </summary>
+ public string Route { get; set; }
+
+ /// <summary>
+ /// Whether to exclude this property from being included in the ModelSchema
+ /// </summary>
+ public bool ExcludeInSchema { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Services/HttpUtility.cs b/MediaBrowser.Model/Services/HttpUtility.cs
new file mode 100644
index 000000000..5cc0cc37d
--- /dev/null
+++ b/MediaBrowser.Model/Services/HttpUtility.cs
@@ -0,0 +1,923 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
+
+namespace MediaBrowser.Model.Services
+{
+ public static class MyHttpUtility
+ {
+ // Must be sorted
+ static readonly long[] entities = new long[] {
+ (long)'A' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24,
+ (long)'A' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'A' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
+ (long)'A' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
+ (long)'A' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24,
+ (long)'A' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24,
+ (long)'A' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
+ (long)'A' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'B' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32,
+ (long)'C' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16,
+ (long)'C' << 56 | (long)'h' << 48 | (long)'i' << 40,
+ (long)'D' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16,
+ (long)'D' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24,
+ (long)'E' << 56 | (long)'T' << 48 | (long)'H' << 40,
+ (long)'E' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'E' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
+ (long)'E' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
+ (long)'E' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8,
+ (long)'E' << 56 | (long)'t' << 48 | (long)'a' << 40,
+ (long)'E' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'G' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24,
+ (long)'I' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'I' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
+ (long)'I' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
+ (long)'I' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32,
+ (long)'I' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'K' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24,
+ (long)'L' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16,
+ (long)'M' << 56 | (long)'u' << 48,
+ (long)'N' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
+ (long)'N' << 56 | (long)'u' << 48,
+ (long)'O' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24,
+ (long)'O' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'O' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
+ (long)'O' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
+ (long)'O' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24,
+ (long)'O' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8,
+ (long)'O' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16,
+ (long)'O' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
+ (long)'O' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'P' << 56 | (long)'h' << 48 | (long)'i' << 40,
+ (long)'P' << 56 | (long)'i' << 48,
+ (long)'P' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24,
+ (long)'P' << 56 | (long)'s' << 48 | (long)'i' << 40,
+ (long)'R' << 56 | (long)'h' << 48 | (long)'o' << 40,
+ (long)'S' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16,
+ (long)'S' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24,
+ (long)'T' << 56 | (long)'H' << 48 | (long)'O' << 40 | (long)'R' << 32 | (long)'N' << 24,
+ (long)'T' << 56 | (long)'a' << 48 | (long)'u' << 40,
+ (long)'T' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24,
+ (long)'U' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'U' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
+ (long)'U' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
+ (long)'U' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8,
+ (long)'U' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'X' << 56 | (long)'i' << 48,
+ (long)'Y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'Y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'Z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32,
+ (long)'a' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'a' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
+ (long)'a' << 56 | (long)'c' << 48 | (long)'u' << 40 | (long)'t' << 32 | (long)'e' << 24,
+ (long)'a' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24,
+ (long)'a' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
+ (long)'a' << 56 | (long)'l' << 48 | (long)'e' << 40 | (long)'f' << 32 | (long)'s' << 24 | (long)'y' << 16 | (long)'m' << 8,
+ (long)'a' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24,
+ (long)'a' << 56 | (long)'m' << 48 | (long)'p' << 40,
+ (long)'a' << 56 | (long)'n' << 48 | (long)'d' << 40,
+ (long)'a' << 56 | (long)'n' << 48 | (long)'g' << 40,
+ (long)'a' << 56 | (long)'p' << 48 | (long)'o' << 40 | (long)'s' << 32,
+ (long)'a' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24,
+ (long)'a' << 56 | (long)'s' << 48 | (long)'y' << 40 | (long)'m' << 32 | (long)'p' << 24,
+ (long)'a' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
+ (long)'a' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'b' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
+ (long)'b' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32,
+ (long)'b' << 56 | (long)'r' << 48 | (long)'v' << 40 | (long)'b' << 32 | (long)'a' << 24 | (long)'r' << 16,
+ (long)'b' << 56 | (long)'u' << 48 | (long)'l' << 40 | (long)'l' << 32,
+ (long)'c' << 56 | (long)'a' << 48 | (long)'p' << 40,
+ (long)'c' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16,
+ (long)'c' << 56 | (long)'e' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'l' << 24,
+ (long)'c' << 56 | (long)'e' << 48 | (long)'n' << 40 | (long)'t' << 32,
+ (long)'c' << 56 | (long)'h' << 48 | (long)'i' << 40,
+ (long)'c' << 56 | (long)'i' << 48 | (long)'r' << 40 | (long)'c' << 32,
+ (long)'c' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'b' << 32 | (long)'s' << 24,
+ (long)'c' << 56 | (long)'o' << 48 | (long)'n' << 40 | (long)'g' << 32,
+ (long)'c' << 56 | (long)'o' << 48 | (long)'p' << 40 | (long)'y' << 32,
+ (long)'c' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'r' << 24,
+ (long)'c' << 56 | (long)'u' << 48 | (long)'p' << 40,
+ (long)'c' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'n' << 16,
+ (long)'d' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32,
+ (long)'d' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16,
+ (long)'d' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32,
+ (long)'d' << 56 | (long)'e' << 48 | (long)'g' << 40,
+ (long)'d' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24,
+ (long)'d' << 56 | (long)'i' << 48 | (long)'a' << 40 | (long)'m' << 32 | (long)'s' << 24,
+ (long)'d' << 56 | (long)'i' << 48 | (long)'v' << 40 | (long)'i' << 32 | (long)'d' << 24 | (long)'e' << 16,
+ (long)'e' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'e' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
+ (long)'e' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
+ (long)'e' << 56 | (long)'m' << 48 | (long)'p' << 40 | (long)'t' << 32 | (long)'y' << 24,
+ (long)'e' << 56 | (long)'m' << 48 | (long)'s' << 40 | (long)'p' << 32,
+ (long)'e' << 56 | (long)'n' << 48 | (long)'s' << 40 | (long)'p' << 32,
+ (long)'e' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8,
+ (long)'e' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'i' << 32 | (long)'v' << 24,
+ (long)'e' << 56 | (long)'t' << 48 | (long)'a' << 40,
+ (long)'e' << 56 | (long)'t' << 48 | (long)'h' << 40,
+ (long)'e' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'e' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'o' << 32,
+ (long)'e' << 56 | (long)'x' << 48 | (long)'i' << 40 | (long)'s' << 32 | (long)'t' << 24,
+ (long)'f' << 56 | (long)'n' << 48 | (long)'o' << 40 | (long)'f' << 32,
+ (long)'f' << 56 | (long)'o' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'l' << 24 | (long)'l' << 16,
+ (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'2' << 16,
+ (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'4' << 16,
+ (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'3' << 24 | (long)'4' << 16,
+ (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'l' << 24,
+ (long)'g' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24,
+ (long)'g' << 56 | (long)'e' << 48,
+ (long)'g' << 56 | (long)'t' << 48,
+ (long)'h' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32,
+ (long)'h' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32,
+ (long)'h' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'t' << 24 | (long)'s' << 16,
+ (long)'h' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'l' << 32 | (long)'i' << 24 | (long)'p' << 16,
+ (long)'i' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'i' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
+ (long)'i' << 56 | (long)'e' << 48 | (long)'x' << 40 | (long)'c' << 32 | (long)'l' << 24,
+ (long)'i' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
+ (long)'i' << 56 | (long)'m' << 48 | (long)'a' << 40 | (long)'g' << 32 | (long)'e' << 24,
+ (long)'i' << 56 | (long)'n' << 48 | (long)'f' << 40 | (long)'i' << 32 | (long)'n' << 24,
+ (long)'i' << 56 | (long)'n' << 48 | (long)'t' << 40,
+ (long)'i' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32,
+ (long)'i' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'e' << 32 | (long)'s' << 24 | (long)'t' << 16,
+ (long)'i' << 56 | (long)'s' << 48 | (long)'i' << 40 | (long)'n' << 32,
+ (long)'i' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'k' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24,
+ (long)'l' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32,
+ (long)'l' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16,
+ (long)'l' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32,
+ (long)'l' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
+ (long)'l' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32,
+ (long)'l' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24,
+ (long)'l' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
+ (long)'l' << 56 | (long)'e' << 48,
+ (long)'l' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16,
+ (long)'l' << 56 | (long)'o' << 48 | (long)'w' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'t' << 16,
+ (long)'l' << 56 | (long)'o' << 48 | (long)'z' << 40,
+ (long)'l' << 56 | (long)'r' << 48 | (long)'m' << 40,
+ (long)'l' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16,
+ (long)'l' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
+ (long)'l' << 56 | (long)'t' << 48,
+ (long)'m' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'r' << 32,
+ (long)'m' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24,
+ (long)'m' << 56 | (long)'i' << 48 | (long)'c' << 40 | (long)'r' << 32 | (long)'o' << 24,
+ (long)'m' << 56 | (long)'i' << 48 | (long)'d' << 40 | (long)'d' << 32 | (long)'o' << 24 | (long)'t' << 16,
+ (long)'m' << 56 | (long)'i' << 48 | (long)'n' << 40 | (long)'u' << 32 | (long)'s' << 24,
+ (long)'m' << 56 | (long)'u' << 48,
+ (long)'n' << 56 | (long)'a' << 48 | (long)'b' << 40 | (long)'l' << 32 | (long)'a' << 24,
+ (long)'n' << 56 | (long)'b' << 48 | (long)'s' << 40 | (long)'p' << 32,
+ (long)'n' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24,
+ (long)'n' << 56 | (long)'e' << 48,
+ (long)'n' << 56 | (long)'i' << 48,
+ (long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40,
+ (long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'i' << 32 | (long)'n' << 24,
+ (long)'n' << 56 | (long)'s' << 48 | (long)'u' << 40 | (long)'b' << 32,
+ (long)'n' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
+ (long)'n' << 56 | (long)'u' << 48,
+ (long)'o' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'o' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
+ (long)'o' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24,
+ (long)'o' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
+ (long)'o' << 56 | (long)'l' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'e' << 24,
+ (long)'o' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24,
+ (long)'o' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8,
+ (long)'o' << 56 | (long)'p' << 48 | (long)'l' << 40 | (long)'u' << 32 | (long)'s' << 24,
+ (long)'o' << 56 | (long)'r' << 48,
+ (long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'f' << 32,
+ (long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'m' << 32,
+ (long)'o' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16,
+ (long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16,
+ (long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24 | (long)'s' << 16,
+ (long)'o' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'a' << 32,
+ (long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'t' << 32,
+ (long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'m' << 32 | (long)'i' << 24 | (long)'l' << 16,
+ (long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'p' << 32,
+ (long)'p' << 56 | (long)'h' << 48 | (long)'i' << 40,
+ (long)'p' << 56 | (long)'i' << 48,
+ (long)'p' << 56 | (long)'i' << 48 | (long)'v' << 40,
+ (long)'p' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'s' << 32 | (long)'m' << 24 | (long)'n' << 16,
+ (long)'p' << 56 | (long)'o' << 48 | (long)'u' << 40 | (long)'n' << 32 | (long)'d' << 24,
+ (long)'p' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24,
+ (long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'d' << 32,
+ (long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'p' << 32,
+ (long)'p' << 56 | (long)'s' << 48 | (long)'i' << 40,
+ (long)'q' << 56 | (long)'u' << 48 | (long)'o' << 40 | (long)'t' << 32,
+ (long)'r' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32,
+ (long)'r' << 56 | (long)'a' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'c' << 24,
+ (long)'r' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32,
+ (long)'r' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
+ (long)'r' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32,
+ (long)'r' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24,
+ (long)'r' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
+ (long)'r' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'l' << 32,
+ (long)'r' << 56 | (long)'e' << 48 | (long)'g' << 40,
+ (long)'r' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16,
+ (long)'r' << 56 | (long)'h' << 48 | (long)'o' << 40,
+ (long)'r' << 56 | (long)'l' << 48 | (long)'m' << 40,
+ (long)'r' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16,
+ (long)'r' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
+ (long)'s' << 56 | (long)'b' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24,
+ (long)'s' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16,
+ (long)'s' << 56 | (long)'d' << 48 | (long)'o' << 40 | (long)'t' << 32,
+ (long)'s' << 56 | (long)'e' << 48 | (long)'c' << 40 | (long)'t' << 32,
+ (long)'s' << 56 | (long)'h' << 48 | (long)'y' << 40,
+ (long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24,
+ (long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24 | (long)'f' << 16,
+ (long)'s' << 56 | (long)'i' << 48 | (long)'m' << 40,
+ (long)'s' << 56 | (long)'p' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24 | (long)'s' << 16,
+ (long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40,
+ (long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40 | (long)'e' << 32,
+ (long)'s' << 56 | (long)'u' << 48 | (long)'m' << 40,
+ (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40,
+ (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'1' << 32,
+ (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'2' << 32,
+ (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'3' << 32,
+ (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'e' << 32,
+ (long)'s' << 56 | (long)'z' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24,
+ (long)'t' << 56 | (long)'a' << 48 | (long)'u' << 40,
+ (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'4' << 16,
+ (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24,
+ (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24 | (long)'s' << 16 | (long)'y' << 8 | (long)'m' << 0,
+ (long)'t' << 56 | (long)'h' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'s' << 24 | (long)'p' << 16,
+ (long)'t' << 56 | (long)'h' << 48 | (long)'o' << 40 | (long)'r' << 32 | (long)'n' << 24,
+ (long)'t' << 56 | (long)'i' << 48 | (long)'l' << 40 | (long)'d' << 32 | (long)'e' << 24,
+ (long)'t' << 56 | (long)'i' << 48 | (long)'m' << 40 | (long)'e' << 32 | (long)'s' << 24,
+ (long)'t' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24,
+ (long)'u' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32,
+ (long)'u' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'u' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32,
+ (long)'u' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24,
+ (long)'u' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16,
+ (long)'u' << 56 | (long)'m' << 48 | (long)'l' << 40,
+ (long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'h' << 24,
+ (long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8,
+ (long)'u' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'w' << 56 | (long)'e' << 48 | (long)'i' << 40 | (long)'e' << 32 | (long)'r' << 24 | (long)'p' << 16,
+ (long)'x' << 56 | (long)'i' << 48,
+ (long)'y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16,
+ (long)'y' << 56 | (long)'e' << 48 | (long)'n' << 40,
+ (long)'y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32,
+ (long)'z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32,
+ (long)'z' << 56 | (long)'w' << 48 | (long)'j' << 40,
+ (long)'z' << 56 | (long)'w' << 48 | (long)'n' << 40 | (long)'j' << 32
+ };
+
+ static readonly char[] entities_values = new char[] {
+ '\u00C6',
+ '\u00C1',
+ '\u00C2',
+ '\u00C0',
+ '\u0391',
+ '\u00C5',
+ '\u00C3',
+ '\u00C4',
+ '\u0392',
+ '\u00C7',
+ '\u03A7',
+ '\u2021',
+ '\u0394',
+ '\u00D0',
+ '\u00C9',
+ '\u00CA',
+ '\u00C8',
+ '\u0395',
+ '\u0397',
+ '\u00CB',
+ '\u0393',
+ '\u00CD',
+ '\u00CE',
+ '\u00CC',
+ '\u0399',
+ '\u00CF',
+ '\u039A',
+ '\u039B',
+ '\u039C',
+ '\u00D1',
+ '\u039D',
+ '\u0152',
+ '\u00D3',
+ '\u00D4',
+ '\u00D2',
+ '\u03A9',
+ '\u039F',
+ '\u00D8',
+ '\u00D5',
+ '\u00D6',
+ '\u03A6',
+ '\u03A0',
+ '\u2033',
+ '\u03A8',
+ '\u03A1',
+ '\u0160',
+ '\u03A3',
+ '\u00DE',
+ '\u03A4',
+ '\u0398',
+ '\u00DA',
+ '\u00DB',
+ '\u00D9',
+ '\u03A5',
+ '\u00DC',
+ '\u039E',
+ '\u00DD',
+ '\u0178',
+ '\u0396',
+ '\u00E1',
+ '\u00E2',
+ '\u00B4',
+ '\u00E6',
+ '\u00E0',
+ '\u2135',
+ '\u03B1',
+ '\u0026',
+ '\u2227',
+ '\u2220',
+ '\u0027',
+ '\u00E5',
+ '\u2248',
+ '\u00E3',
+ '\u00E4',
+ '\u201E',
+ '\u03B2',
+ '\u00A6',
+ '\u2022',
+ '\u2229',
+ '\u00E7',
+ '\u00B8',
+ '\u00A2',
+ '\u03C7',
+ '\u02C6',
+ '\u2663',
+ '\u2245',
+ '\u00A9',
+ '\u21B5',
+ '\u222A',
+ '\u00A4',
+ '\u21D3',
+ '\u2020',
+ '\u2193',
+ '\u00B0',
+ '\u03B4',
+ '\u2666',
+ '\u00F7',
+ '\u00E9',
+ '\u00EA',
+ '\u00E8',
+ '\u2205',
+ '\u2003',
+ '\u2002',
+ '\u03B5',
+ '\u2261',
+ '\u03B7',
+ '\u00F0',
+ '\u00EB',
+ '\u20AC',
+ '\u2203',
+ '\u0192',
+ '\u2200',
+ '\u00BD',
+ '\u00BC',
+ '\u00BE',
+ '\u2044',
+ '\u03B3',
+ '\u2265',
+ '\u003E',
+ '\u21D4',
+ '\u2194',
+ '\u2665',
+ '\u2026',
+ '\u00ED',
+ '\u00EE',
+ '\u00A1',
+ '\u00EC',
+ '\u2111',
+ '\u221E',
+ '\u222B',
+ '\u03B9',
+ '\u00BF',
+ '\u2208',
+ '\u00EF',
+ '\u03BA',
+ '\u21D0',
+ '\u03BB',
+ '\u2329',
+ '\u00AB',
+ '\u2190',
+ '\u2308',
+ '\u201C',
+ '\u2264',
+ '\u230A',
+ '\u2217',
+ '\u25CA',
+ '\u200E',
+ '\u2039',
+ '\u2018',
+ '\u003C',
+ '\u00AF',
+ '\u2014',
+ '\u00B5',
+ '\u00B7',
+ '\u2212',
+ '\u03BC',
+ '\u2207',
+ '\u00A0',
+ '\u2013',
+ '\u2260',
+ '\u220B',
+ '\u00AC',
+ '\u2209',
+ '\u2284',
+ '\u00F1',
+ '\u03BD',
+ '\u00F3',
+ '\u00F4',
+ '\u0153',
+ '\u00F2',
+ '\u203E',
+ '\u03C9',
+ '\u03BF',
+ '\u2295',
+ '\u2228',
+ '\u00AA',
+ '\u00BA',
+ '\u00F8',
+ '\u00F5',
+ '\u2297',
+ '\u00F6',
+ '\u00B6',
+ '\u2202',
+ '\u2030',
+ '\u22A5',
+ '\u03C6',
+ '\u03C0',
+ '\u03D6',
+ '\u00B1',
+ '\u00A3',
+ '\u2032',
+ '\u220F',
+ '\u221D',
+ '\u03C8',
+ '\u0022',
+ '\u21D2',
+ '\u221A',
+ '\u232A',
+ '\u00BB',
+ '\u2192',
+ '\u2309',
+ '\u201D',
+ '\u211C',
+ '\u00AE',
+ '\u230B',
+ '\u03C1',
+ '\u200F',
+ '\u203A',
+ '\u2019',
+ '\u201A',
+ '\u0161',
+ '\u22C5',
+ '\u00A7',
+ '\u00AD',
+ '\u03C3',
+ '\u03C2',
+ '\u223C',
+ '\u2660',
+ '\u2282',
+ '\u2286',
+ '\u2211',
+ '\u2283',
+ '\u00B9',
+ '\u00B2',
+ '\u00B3',
+ '\u2287',
+ '\u00DF',
+ '\u03C4',
+ '\u2234',
+ '\u03B8',
+ '\u03D1',
+ '\u2009',
+ '\u00FE',
+ '\u02DC',
+ '\u00D7',
+ '\u2122',
+ '\u21D1',
+ '\u00FA',
+ '\u2191',
+ '\u00FB',
+ '\u00F9',
+ '\u00A8',
+ '\u03D2',
+ '\u03C5',
+ '\u00FC',
+ '\u2118',
+ '\u03BE',
+ '\u00FD',
+ '\u00A5',
+ '\u00FF',
+ '\u03B6',
+ '\u200D',
+ '\u200C'
+ };
+
+ #region Methods
+
+ static void WriteCharBytes(IList buf, char ch, Encoding e)
+ {
+ if (ch > 255)
+ {
+ foreach (byte b in e.GetBytes(new char[] { ch }))
+ buf.Add(b);
+ }
+ else
+ buf.Add((byte)ch);
+ }
+
+ public static string UrlDecode(string s, Encoding e)
+ {
+ if (null == s)
+ return null;
+
+ if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1)
+ return s;
+
+ if (e == null)
+ e = Encoding.UTF8;
+
+ long len = s.Length;
+ var bytes = new List<byte>();
+ int xchar;
+ char ch;
+
+ for (int i = 0; i < len; i++)
+ {
+ ch = s[i];
+ if (ch == '%' && i + 2 < len && s[i + 1] != '%')
+ {
+ if (s[i + 1] == 'u' && i + 5 < len)
+ {
+ // unicode hex sequence
+ xchar = GetChar(s, i + 2, 4);
+ if (xchar != -1)
+ {
+ WriteCharBytes(bytes, (char)xchar, e);
+ i += 5;
+ }
+ else
+ WriteCharBytes(bytes, '%', e);
+ }
+ else if ((xchar = GetChar(s, i + 1, 2)) != -1)
+ {
+ WriteCharBytes(bytes, (char)xchar, e);
+ i += 2;
+ }
+ else
+ {
+ WriteCharBytes(bytes, '%', e);
+ }
+ continue;
+ }
+
+ if (ch == '+')
+ WriteCharBytes(bytes, ' ', e);
+ else
+ WriteCharBytes(bytes, ch, e);
+ }
+
+ byte[] buf = bytes.ToArray(bytes.Count);
+ bytes = null;
+ return e.GetString(buf, 0, buf.Length);
+
+ }
+
+ static int GetInt(byte b)
+ {
+ char c = (char)b;
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+
+ return -1;
+ }
+
+ static int GetChar(string str, int offset, int length)
+ {
+ int val = 0;
+ int end = length + offset;
+ for (int i = offset; i < end; i++)
+ {
+ char c = str[i];
+ if (c > 127)
+ return -1;
+
+ int current = GetInt((byte)c);
+ if (current == -1)
+ return -1;
+ val = (val << 4) + current;
+ }
+
+ return val;
+ }
+
+ static bool TryConvertKeyToEntity(string key, out char value)
+ {
+ var token = CalculateKeyValue(key);
+ if (token == 0)
+ {
+ value = '\0';
+ return false;
+ }
+
+ var idx = Array.BinarySearch(entities, token);
+ if (idx < 0)
+ {
+ value = '\0';
+ return false;
+ }
+
+ value = entities_values[idx];
+ return true;
+ }
+
+ static long CalculateKeyValue(string s)
+ {
+ if (s.Length > 8)
+ return 0;
+
+ long key = 0;
+ for (int i = 0; i < s.Length; ++i)
+ {
+ long ch = s[i];
+ if (ch > 'z' || ch < '0')
+ return 0;
+
+ key |= ch << ((7 - i) * 8);
+ }
+
+ return key;
+ }
+
+ /// <summary>
+ /// Decodes an HTML-encoded string and returns the decoded string.
+ /// </summary>
+ /// <param name="s">The HTML string to decode. </param>
+ /// <returns>The decoded text.</returns>
+ public static string HtmlDecode(string s)
+ {
+ if (s == null)
+ throw new ArgumentNullException("s");
+
+ if (s.IndexOf('&') == -1)
+ return s;
+
+ StringBuilder entity = new StringBuilder();
+ StringBuilder output = new StringBuilder();
+ int len = s.Length;
+ // 0 -> nothing,
+ // 1 -> right after '&'
+ // 2 -> between '&' and ';' but no '#'
+ // 3 -> '#' found after '&' and getting numbers
+ int state = 0;
+ int number = 0;
+ int digit_start = 0;
+ bool hex_number = false;
+
+ for (int i = 0; i < len; i++)
+ {
+ char c = s[i];
+ if (state == 0)
+ {
+ if (c == '&')
+ {
+ entity.Append(c);
+ state = 1;
+ }
+ else
+ {
+ output.Append(c);
+ }
+ continue;
+ }
+
+ if (c == '&')
+ {
+ state = 1;
+ if (digit_start > 0)
+ {
+ entity.Append(s, digit_start, i - digit_start);
+ digit_start = 0;
+ }
+
+ output.Append(entity.ToString());
+ entity.Length = 0;
+ entity.Append('&');
+ continue;
+ }
+
+ switch (state)
+ {
+ case 1:
+ if (c == ';')
+ {
+ state = 0;
+ output.Append(entity.ToString());
+ output.Append(c);
+ entity.Length = 0;
+ break;
+ }
+
+ number = 0;
+ hex_number = false;
+ if (c != '#')
+ {
+ state = 2;
+ }
+ else
+ {
+ state = 3;
+ }
+ entity.Append(c);
+
+ break;
+ case 2:
+ entity.Append(c);
+ if (c == ';')
+ {
+ string key = entity.ToString();
+ state = 0;
+ entity.Length = 0;
+
+ if (key.Length > 1)
+ {
+ var skey = key.Substring(1, key.Length - 2);
+ if (TryConvertKeyToEntity(skey, out c))
+ {
+ output.Append(c);
+ break;
+ }
+ }
+
+ output.Append(key);
+ }
+
+ break;
+ case 3:
+ if (c == ';')
+ {
+ if (number < 0x10000)
+ {
+ output.Append((char)number);
+ }
+ else
+ {
+ output.Append((char)(0xd800 + ((number - 0x10000) >> 10)));
+ output.Append((char)(0xdc00 + ((number - 0x10000) & 0x3ff)));
+ }
+ state = 0;
+ entity.Length = 0;
+ digit_start = 0;
+ break;
+ }
+
+ if (c == 'x' || c == 'X' && !hex_number)
+ {
+ digit_start = i;
+ hex_number = true;
+ break;
+ }
+
+ if (Char.IsDigit(c))
+ {
+ if (digit_start == 0)
+ digit_start = i;
+
+ number = number * (hex_number ? 16 : 10) + ((int)c - '0');
+ break;
+ }
+
+ if (hex_number)
+ {
+ if (c >= 'a' && c <= 'f')
+ {
+ number = number * 16 + 10 + ((int)c - 'a');
+ break;
+ }
+ if (c >= 'A' && c <= 'F')
+ {
+ number = number * 16 + 10 + ((int)c - 'A');
+ break;
+ }
+ }
+
+ state = 2;
+ if (digit_start > 0)
+ {
+ entity.Append(s, digit_start, i - digit_start);
+ digit_start = 0;
+ }
+
+ entity.Append(c);
+ break;
+ }
+ }
+
+ if (entity.Length > 0)
+ {
+ output.Append(entity);
+ }
+ else if (digit_start > 0)
+ {
+ output.Append(s, digit_start, s.Length - digit_start);
+ }
+ return output.ToString();
+ }
+
+ public static QueryParamCollection ParseQueryString(string query)
+ {
+ return ParseQueryString(query, Encoding.UTF8);
+ }
+
+ public static QueryParamCollection ParseQueryString(string query, Encoding encoding)
+ {
+ if (query == null)
+ throw new ArgumentNullException("query");
+ if (encoding == null)
+ throw new ArgumentNullException("encoding");
+ if (query.Length == 0 || (query.Length == 1 && query[0] == '?'))
+ return new QueryParamCollection();
+ if (query[0] == '?')
+ query = query.Substring(1);
+
+ QueryParamCollection result = new QueryParamCollection();
+ ParseQueryString(query, encoding, result);
+ return result;
+ }
+
+ internal static void ParseQueryString(string query, Encoding encoding, QueryParamCollection result)
+ {
+ if (query.Length == 0)
+ return;
+
+ string decoded = HtmlDecode(query);
+ int decodedLength = decoded.Length;
+ int namePos = 0;
+ bool first = true;
+ while (namePos <= decodedLength)
+ {
+ int valuePos = -1, valueEnd = -1;
+ for (int q = namePos; q < decodedLength; q++)
+ {
+ if (valuePos == -1 && decoded[q] == '=')
+ {
+ valuePos = q + 1;
+ }
+ else if (decoded[q] == '&')
+ {
+ valueEnd = q;
+ break;
+ }
+ }
+
+ if (first)
+ {
+ first = false;
+ if (decoded[namePos] == '?')
+ namePos++;
+ }
+
+ string name, value;
+ if (valuePos == -1)
+ {
+ name = null;
+ valuePos = namePos;
+ }
+ else
+ {
+ name = UrlDecode(decoded.Substring(namePos, valuePos - namePos - 1), encoding);
+ }
+ if (valueEnd < 0)
+ {
+ namePos = -1;
+ valueEnd = decoded.Length;
+ }
+ else
+ {
+ namePos = valueEnd + 1;
+ }
+ value = UrlDecode(decoded.Substring(valuePos, valueEnd - valuePos), encoding);
+
+ result.Add(name, value);
+ if (namePos == -1)
+ break;
+ }
+ }
+ #endregion // Methods
+ }
+}
diff --git a/MediaBrowser.Model/Services/IAsyncStreamWriter.cs b/MediaBrowser.Model/Services/IAsyncStreamWriter.cs
new file mode 100644
index 000000000..b10e12813
--- /dev/null
+++ b/MediaBrowser.Model/Services/IAsyncStreamWriter.cs
@@ -0,0 +1,11 @@
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Model.Services
+{
+ public interface IAsyncStreamWriter
+ {
+ Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken);
+ }
+}
diff --git a/MediaBrowser.Model/Services/IHasHeaders.cs b/MediaBrowser.Model/Services/IHasHeaders.cs
new file mode 100644
index 000000000..35e652b0f
--- /dev/null
+++ b/MediaBrowser.Model/Services/IHasHeaders.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.Services
+{
+ public interface IHasHeaders
+ {
+ IDictionary<string, string> Headers { get; }
+ }
+}
diff --git a/MediaBrowser.Model/Services/IHasRequestFilter.cs b/MediaBrowser.Model/Services/IHasRequestFilter.cs
new file mode 100644
index 000000000..2164179d5
--- /dev/null
+++ b/MediaBrowser.Model/Services/IHasRequestFilter.cs
@@ -0,0 +1,21 @@
+
+namespace MediaBrowser.Model.Services
+{
+ public interface IHasRequestFilter
+ {
+ /// <summary>
+ /// Order in which Request Filters are executed.
+ /// &lt;0 Executed before global request filters
+ /// &gt;0 Executed after global request filters
+ /// </summary>
+ int Priority { get; }
+
+ /// <summary>
+ /// The request filter is executed before the service.
+ /// </summary>
+ /// <param name="req">The http request wrapper</param>
+ /// <param name="res">The http response wrapper</param>
+ /// <param name="requestDto">The request DTO</param>
+ void RequestFilter(IRequest req, IResponse res, object requestDto);
+ }
+}
diff --git a/MediaBrowser.Model/Services/IHttpRequest.cs b/MediaBrowser.Model/Services/IHttpRequest.cs
new file mode 100644
index 000000000..e1480f30a
--- /dev/null
+++ b/MediaBrowser.Model/Services/IHttpRequest.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Model.Services
+{
+ public interface IHttpRequest : IRequest
+ {
+ /// <summary>
+ /// The HttpResponse
+ /// </summary>
+ IHttpResponse HttpResponse { get; }
+
+ /// <summary>
+ /// The HTTP Verb
+ /// </summary>
+ string HttpMethod { get; }
+
+ /// <summary>
+ /// The IP Address of the X-Forwarded-For header, null if null or empty
+ /// </summary>
+ string XForwardedFor { get; }
+
+ /// <summary>
+ /// The Port number of the X-Forwarded-Port header, null if null or empty
+ /// </summary>
+ int? XForwardedPort { get; }
+
+ /// <summary>
+ /// The http or https scheme of the X-Forwarded-Proto header, null if null or empty
+ /// </summary>
+ string XForwardedProtocol { get; }
+
+ /// <summary>
+ /// The value of the X-Real-IP header, null if null or empty
+ /// </summary>
+ string XRealIp { get; }
+
+ /// <summary>
+ /// The value of the Accept HTTP Request Header
+ /// </summary>
+ string Accept { get; }
+ }
+}
diff --git a/MediaBrowser.Model/Services/IHttpResponse.cs b/MediaBrowser.Model/Services/IHttpResponse.cs
new file mode 100644
index 000000000..cd9c07d46
--- /dev/null
+++ b/MediaBrowser.Model/Services/IHttpResponse.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Model.Services
+{
+ public interface IHttpResponse : IResponse
+ {
+ //ICookies Cookies { get; }
+
+ /// <summary>
+ /// Adds a new Set-Cookie instruction to Response
+ /// </summary>
+ /// <param name="cookie"></param>
+ void SetCookie(Cookie cookie);
+
+ /// <summary>
+ /// Removes all pending Set-Cookie instructions
+ /// </summary>
+ void ClearCookies();
+ }
+}
diff --git a/MediaBrowser.Model/Services/IHttpResult.cs b/MediaBrowser.Model/Services/IHttpResult.cs
new file mode 100644
index 000000000..b912ef023
--- /dev/null
+++ b/MediaBrowser.Model/Services/IHttpResult.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Model.Services
+{
+ public interface IHttpResult : IHasHeaders
+ {
+ /// <summary>
+ /// The HTTP Response Status
+ /// </summary>
+ int Status { get; set; }
+
+ /// <summary>
+ /// The HTTP Response Status Code
+ /// </summary>
+ HttpStatusCode StatusCode { get; set; }
+
+ /// <summary>
+ /// The HTTP Response ContentType
+ /// </summary>
+ string ContentType { get; set; }
+
+ /// <summary>
+ /// Response DTO
+ /// </summary>
+ object Response { get; set; }
+
+ /// <summary>
+ /// Holds the request call context
+ /// </summary>
+ IRequest RequestContext { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs
new file mode 100644
index 000000000..681bab294
--- /dev/null
+++ b/MediaBrowser.Model/Services/IRequest.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
+
+namespace MediaBrowser.Model.Services
+{
+ public interface IRequest
+ {
+ /// <summary>
+ /// The underlying ASP.NET or HttpListener HttpRequest
+ /// </summary>
+ object OriginalRequest { get; }
+
+ IResponse Response { get; }
+
+ /// <summary>
+ /// The name of the service being called (e.g. Request DTO Name)
+ /// </summary>
+ string OperationName { get; set; }
+
+ /// <summary>
+ /// The Verb / HttpMethod or Action for this request
+ /// </summary>
+ string Verb { get; }
+
+ /// <summary>
+ /// The Request DTO, after it has been deserialized.
+ /// </summary>
+ object Dto { get; set; }
+
+ /// <summary>
+ /// The request ContentType
+ /// </summary>
+ string ContentType { get; }
+
+ bool IsLocal { get; }
+
+ string UserAgent { get; }
+
+ IDictionary<string, Cookie> Cookies { get; }
+
+ /// <summary>
+ /// The expected Response ContentType for this request
+ /// </summary>
+ string ResponseContentType { get; set; }
+
+ /// <summary>
+ /// Attach any data to this request that all filters and services can access.
+ /// </summary>
+ Dictionary<string, object> Items { get; }
+
+ QueryParamCollection Headers { get; }
+
+ QueryParamCollection QueryString { get; }
+
+ Task<QueryParamCollection> GetFormData();
+
+ string RawUrl { get; }
+
+ string AbsoluteUri { get; }
+
+ /// <summary>
+ /// The Remote Ip as reported by Request.UserHostAddress
+ /// </summary>
+ string UserHostAddress { get; }
+
+ /// <summary>
+ /// The Remote Ip as reported by X-Forwarded-For, X-Real-IP or Request.UserHostAddress
+ /// </summary>
+ string RemoteIp { get; }
+
+ /// <summary>
+ /// The value of the Authorization Header used to send the Api Key, null if not available
+ /// </summary>
+ string Authorization { get; }
+
+ /// <summary>
+ /// e.g. is https or not
+ /// </summary>
+ bool IsSecureConnection { get; }
+
+ string[] AcceptTypes { get; }
+
+ string PathInfo { get; }
+
+ Stream InputStream { get; }
+
+ long ContentLength { get; }
+
+ /// <summary>
+ /// Access to the multi-part/formdata files posted on this request
+ /// </summary>
+ IHttpFile[] Files { get; }
+
+ /// <summary>
+ /// The value of the Referrer, null if not available
+ /// </summary>
+ Uri UrlReferrer { get; }
+ }
+
+ public interface IHttpFile
+ {
+ string Name { get; }
+ string FileName { get; }
+ long ContentLength { get; }
+ string ContentType { get; }
+ Stream InputStream { get; }
+ }
+
+ public interface IRequiresRequest
+ {
+ IRequest Request { get; set; }
+ }
+
+ public interface IResponse
+ {
+ IRequest Request { get; }
+
+ int StatusCode { get; set; }
+
+ string StatusDescription { get; set; }
+
+ string ContentType { get; set; }
+
+ void AddHeader(string name, string value);
+
+ string GetHeader(string name);
+
+ void Redirect(string url);
+
+ Stream OutputStream { get; }
+
+ /// <summary>
+ /// Signal that this response has been handled and no more processing should be done.
+ /// When used in a request or response filter, no more filters or processing is done on this request.
+ /// </summary>
+ void Close();
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is closed.
+ /// </summary>
+ bool IsClosed { get; }
+
+ void SetContentLength(long contentLength);
+
+ //Add Metadata to Response
+ Dictionary<string, object> Items { get; }
+
+ QueryParamCollection Headers { get; }
+
+ Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken);
+
+ bool SendChunked { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Services/IRequiresRequestStream.cs b/MediaBrowser.Model/Services/IRequiresRequestStream.cs
new file mode 100644
index 000000000..0b8ac3ed3
--- /dev/null
+++ b/MediaBrowser.Model/Services/IRequiresRequestStream.cs
@@ -0,0 +1,12 @@
+using System.IO;
+
+namespace MediaBrowser.Model.Services
+{
+ public interface IRequiresRequestStream
+ {
+ /// <summary>
+ /// The raw Http Request Input Stream
+ /// </summary>
+ Stream RequestStream { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Services/IService.cs b/MediaBrowser.Model/Services/IService.cs
new file mode 100644
index 000000000..3e0ff280b
--- /dev/null
+++ b/MediaBrowser.Model/Services/IService.cs
@@ -0,0 +1,12 @@
+
+namespace MediaBrowser.Model.Services
+{
+ // marker interface
+ public interface IService
+ {
+ }
+
+ public interface IReturn { }
+ public interface IReturn<T> : IReturn { }
+ public interface IReturnVoid : IReturn { }
+}
diff --git a/MediaBrowser.Model/Services/IStreamWriter.cs b/MediaBrowser.Model/Services/IStreamWriter.cs
new file mode 100644
index 000000000..1fc11049e
--- /dev/null
+++ b/MediaBrowser.Model/Services/IStreamWriter.cs
@@ -0,0 +1,9 @@
+using System.IO;
+
+namespace MediaBrowser.Model.Services
+{
+ public interface IStreamWriter
+ {
+ void WriteTo(Stream responseStream);
+ }
+}
diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs
new file mode 100644
index 000000000..6f8a76598
--- /dev/null
+++ b/MediaBrowser.Model/Services/QueryParamCollection.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Extensions;
+
+namespace MediaBrowser.Model.Services
+{
+ public class QueryParamCollection : List<NameValuePair>
+ {
+ public QueryParamCollection()
+ {
+
+ }
+
+ public QueryParamCollection(IDictionary<string, string> headers)
+ {
+ foreach (var pair in headers)
+ {
+ Add(pair.Key, pair.Value);
+ }
+ }
+
+ private StringComparison GetStringComparison()
+ {
+ return StringComparison.OrdinalIgnoreCase;
+ }
+
+ private StringComparer GetStringComparer()
+ {
+ return StringComparer.OrdinalIgnoreCase;
+ }
+
+ public string GetKey(int index)
+ {
+ return this[index].Name;
+ }
+
+ public string Get(int index)
+ {
+ return this[index].Value;
+ }
+
+ public virtual string[] GetValues(int index)
+ {
+ return new[] { Get(index) };
+ }
+
+ /// <summary>
+ /// Adds a new query parameter.
+ /// </summary>
+ public virtual void Add(string key, string value)
+ {
+ Add(new NameValuePair(key, value));
+ }
+
+ public virtual void Set(string key, string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ var parameters = GetItems(key);
+
+ foreach (var p in parameters)
+ {
+ Remove(p);
+ }
+
+ return;
+ }
+
+ foreach (var pair in this)
+ {
+ var stringComparison = GetStringComparison();
+
+ if (string.Equals(key, pair.Name, stringComparison))
+ {
+ pair.Value = value;
+ return;
+ }
+ }
+
+ Add(key, value);
+ }
+
+ /// <summary>
+ /// Removes all parameters of the given name.
+ /// </summary>
+ /// <returns>The number of parameters that were removed</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="name" /> is null.</exception>
+ public virtual int Remove(string name)
+ {
+ return RemoveAll(p => p.Name == name);
+ }
+
+ public string Get(string name)
+ {
+ var stringComparison = GetStringComparison();
+
+ foreach (var pair in this)
+ {
+ if (string.Equals(pair.Name, name, stringComparison))
+ {
+ return pair.Value;
+ }
+ }
+
+ return null;
+ }
+
+ public virtual List<NameValuePair> GetItems(string name)
+ {
+ var stringComparison = GetStringComparison();
+
+ var list = new List<NameValuePair>();
+
+ foreach (var pair in this)
+ {
+ if (string.Equals(pair.Name, name, stringComparison))
+ {
+ list.Add(pair);
+ }
+ }
+
+ return list;
+ }
+
+ public virtual List<string> GetValues(string name)
+ {
+ var stringComparison = GetStringComparison();
+
+ var list = new List<string>();
+
+ foreach (var pair in this)
+ {
+ if (string.Equals(pair.Name, name, stringComparison))
+ {
+ list.Add(pair.Value);
+ }
+ }
+
+ return list;
+ }
+
+ public Dictionary<string, string> ToDictionary()
+ {
+ var stringComparer = GetStringComparer();
+
+ var headers = new Dictionary<string, string>(stringComparer);
+
+ foreach (var pair in this)
+ {
+ headers[pair.Name] = pair.Value;
+ }
+
+ return headers;
+ }
+
+ public IEnumerable<string> Keys
+ {
+ get
+ {
+ var keys = new string[this.Count];
+
+ for (var i = 0; i < keys.Length; i++)
+ {
+ keys[i] = this[i].Name;
+ }
+
+ return keys;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a query parameter value by name. A query may contain multiple values of the same name
+ /// (i.e. "x=1&amp;x=2"), in which case the value is an array, which works for both getting and setting.
+ /// </summary>
+ /// <param name="name">The query parameter name</param>
+ /// <returns>The query parameter value or array of values</returns>
+ public string this[string name]
+ {
+ get { return Get(name); }
+ set
+ {
+ Set(name, value);
+ //var parameters = this.Where(p => p.Name == name).ToArray();
+ //var values = new[] { value };
+
+ //for (int i = 0; ; i++)
+ //{
+ // if (i < parameters.Length && i < values.Length)
+ // {
+ // if (values[i] == null)
+ // Remove(parameters[i]);
+ // else if (values[i] is NameValuePair)
+ // this[IndexOf(parameters[i])] = (NameValuePair)values[i];
+ // else
+ // parameters[i].Value = values[i];
+ // }
+ // else if (i < parameters.Length)
+ // Remove(parameters[i]);
+ // else if (i < values.Length)
+ // {
+ // if (values[i] != null)
+ // {
+ // if (values[i] is NameValuePair)
+ // Add((NameValuePair)values[i]);
+ // else
+ // Add(name, values[i]);
+ // }
+ // }
+ // else
+ // break;
+ //}
+ }
+ }
+
+ private string GetQueryStringValue(NameValuePair pair)
+ {
+ return pair.Name + "=" + pair.Value;
+ }
+
+ public override String ToString()
+ {
+ var vals = this.Select(GetQueryStringValue).ToArray(this.Count);
+
+ return string.Join("&", vals);
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Services/RouteAttribute.cs b/MediaBrowser.Model/Services/RouteAttribute.cs
new file mode 100644
index 000000000..264500e60
--- /dev/null
+++ b/MediaBrowser.Model/Services/RouteAttribute.cs
@@ -0,0 +1,148 @@
+using System;
+
+namespace MediaBrowser.Model.Services
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
+ public class RouteAttribute : Attribute
+ {
+ /// <summary>
+ /// <para>Initializes an instance of the <see cref="RouteAttribute"/> class.</para>
+ /// </summary>
+ /// <param name="path">
+ /// <para>The path template to map to the request. See
+ /// <see cref="Path">RouteAttribute.Path</see>
+ /// for details on the correct format.</para>
+ /// </param>
+ public RouteAttribute(string path)
+ : this(path, null)
+ {
+ }
+
+ /// <summary>
+ /// <para>Initializes an instance of the <see cref="RouteAttribute"/> class.</para>
+ /// </summary>
+ /// <param name="path">
+ /// <para>The path template to map to the request. See
+ /// <see cref="Path">RouteAttribute.Path</see>
+ /// for details on the correct format.</para>
+ /// </param>
+ /// <param name="verbs">A comma-delimited list of HTTP verbs supported by the
+ /// service. If unspecified, all verbs are assumed to be supported.</param>
+ public RouteAttribute(string path, string verbs)
+ {
+ Path = path;
+ Verbs = verbs;
+ }
+
+ /// <summary>
+ /// Gets or sets the path template to be mapped to the request.
+ /// </summary>
+ /// <value>
+ /// A <see cref="String"/> value providing the path mapped to
+ /// the request. Never <see langword="null"/>.
+ /// </value>
+ /// <remarks>
+ /// <para>Some examples of valid paths are:</para>
+ ///
+ /// <list>
+ /// <item>"/Inventory"</item>
+ /// <item>"/Inventory/{Category}/{ItemId}"</item>
+ /// <item>"/Inventory/{ItemPath*}"</item>
+ /// </list>
+ ///
+ /// <para>Variables are specified within "{}"
+ /// brackets. Each variable in the path is mapped to the same-named property
+ /// on the request DTO. At runtime, ServiceStack will parse the
+ /// request URL, extract the variable values, instantiate the request DTO,
+ /// and assign the variable values into the corresponding request properties,
+ /// prior to passing the request DTO to the service object for processing.</para>
+ ///
+ /// <para>It is not necessary to specify all request properties as
+ /// variables in the path. For unspecified properties, callers may provide
+ /// values in the query string. For example: the URL
+ /// "http://services/Inventory?Category=Books&amp;ItemId=12345" causes the same
+ /// request DTO to be processed as "http://services/Inventory/Books/12345",
+ /// provided that the paths "/Inventory" (which supports the first URL) and
+ /// "/Inventory/{Category}/{ItemId}" (which supports the second URL)
+ /// are both mapped to the request DTO.</para>
+ ///
+ /// <para>Please note that while it is possible to specify property values
+ /// in the query string, it is generally considered to be less RESTful and
+ /// less desirable than to specify them as variables in the path. Using the
+ /// query string to specify property values may also interfere with HTTP
+ /// caching.</para>
+ ///
+ /// <para>The final variable in the path may contain a "*" suffix
+ /// to grab all remaining segments in the path portion of the request URL and assign
+ /// them to a single property on the request DTO.
+ /// For example, if the path "/Inventory/{ItemPath*}" is mapped to the request DTO,
+ /// then the request URL "http://services/Inventory/Books/12345" will result
+ /// in a request DTO whose ItemPath property contains "Books/12345".
+ /// You may only specify one such variable in the path, and it must be positioned at
+ /// the end of the path.</para>
+ /// </remarks>
+ public string Path { get; set; }
+
+ /// <summary>
+ /// Gets or sets short summary of what the route does.
+ /// </summary>
+ public string Summary { get; set; }
+
+ public string Description { get; set; }
+
+ public bool IsHidden { get; set; }
+
+ /// <summary>
+ /// Gets or sets longer text to explain the behaviour of the route.
+ /// </summary>
+ public string Notes { get; set; }
+
+ /// <summary>
+ /// Gets or sets a comma-delimited list of HTTP verbs supported by the service, such as
+ /// "GET,PUT,POST,DELETE".
+ /// </summary>
+ /// <value>
+ /// A <see cref="String"/> providing a comma-delimited list of HTTP verbs supported
+ /// by the service, <see langword="null"/> or empty if all verbs are supported.
+ /// </value>
+ public string Verbs { get; set; }
+
+ /// <summary>
+ /// Used to rank the precedences of route definitions in reverse routing.
+ /// i.e. Priorities below 0 are auto-generated have less precedence.
+ /// </summary>
+ public int Priority { get; set; }
+
+ protected bool Equals(RouteAttribute other)
+ {
+ return base.Equals(other)
+ && string.Equals(Path, other.Path)
+ && string.Equals(Summary, other.Summary)
+ && string.Equals(Notes, other.Notes)
+ && string.Equals(Verbs, other.Verbs)
+ && Priority == other.Priority;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((RouteAttribute)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = base.GetHashCode();
+ hashCode = (hashCode * 397) ^ (Path != null ? Path.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (Summary != null ? Summary.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (Notes != null ? Notes.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (Verbs != null ? Verbs.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ Priority;
+ return hashCode;
+ }
+ }
+ }
+}