mirror of
git://opensimulator.org/git/opensim-libs
synced 2026-08-14 00:58:00 +00:00
remove httpserver that is no longer used
This commit is contained in:
@@ -1,69 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
1. You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
2. You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
@@ -1,67 +0,0 @@
|
||||
<Configuration>
|
||||
<SettingsComponent>
|
||||
<string />
|
||||
<integer />
|
||||
<boolean>
|
||||
<setting name="SolutionAnalysisEnabled">True</setting>
|
||||
</boolean>
|
||||
</SettingsComponent>
|
||||
<NAntValidationSettings>
|
||||
<NAntPath value="" />
|
||||
</NAntValidationSettings>
|
||||
<CompletionStatisticsManager>
|
||||
<ItemStatistics item="Default">
|
||||
<Item value="private" priority="0" />
|
||||
<Item value="Socket" priority="2" />
|
||||
<Item value="_sock" priority="0" />
|
||||
<Item value="true" priority="0" />
|
||||
<Item value="try" priority="0" />
|
||||
<Item value="catch" priority="0" />
|
||||
<Item value="ObjectDisposedException" priority="0" />
|
||||
<Item value="ILogWriter" priority="0" />
|
||||
</ItemStatistics>
|
||||
<ItemStatistics item="Qualified:System.Net.Sockets.Socket">
|
||||
<Item value="Disconnect" priority="0" />
|
||||
</ItemStatistics>
|
||||
</CompletionStatisticsManager>
|
||||
<RecentFiles>
|
||||
<RecentFiles>
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:Check.cs" caret="143" fromTop="7" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:IHttpResponse.cs" caret="1365" fromTop="19" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/d:Parser/f:RequestParserFactory.cs" caret="542" fromTop="16" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpResponse.cs" caret="12518" fromTop="129" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/d:Parser/f:HttpRequestParser.cs" caret="18853" fromTop="16" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:IHttpRequest.cs" caret="3940" fromTop="16" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpRequest.cs" caret="1478" fromTop="23" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpClientContext.cs" caret="7561" fromTop="15" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpListenerBase.cs" caret="5387" fromTop="27" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpContextFactory.cs" caret="13436" fromTop="33" />
|
||||
</RecentFiles>
|
||||
<RecentEdits>
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpResponse.cs" caret="4833" fromTop="27" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpResponse.cs" caret="4663" fromTop="25" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpResponse.cs" caret="1846" fromTop="15" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:IHttpResponse.cs" caret="1555" fromTop="23" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpClientContext.cs" caret="2211" fromTop="14" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpClientContext.cs" caret="2574" fromTop="21" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpClientContext.cs" caret="869" fromTop="16" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpResponse.cs" caret="11610" fromTop="15" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpContextFactory.cs" caret="14802" fromTop="25" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpContextFactory.cs" caret="14802" fromTop="16" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpContextFactory.cs" caret="14802" fromTop="25" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpContextFactory.cs" caret="14802" fromTop="22" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpContextFactory.cs" caret="14802" fromTop="16" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpRequest.cs" caret="14651" fromTop="23" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpContextFactory.cs" caret="13467" fromTop="22" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpClientContext.cs" caret="7560" fromTop="15" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpListenerBase.cs" caret="5386" fromTop="27" />
|
||||
<File id="455E7D70-1C85-4D7F-9F01-DC801B8B8C34/f:HttpContextFactory.cs" caret="13467" fromTop="33" />
|
||||
</RecentEdits>
|
||||
</RecentFiles>
|
||||
<UnitTestRunner>
|
||||
<Providers />
|
||||
</UnitTestRunner>
|
||||
<UnitTestRunnerMSTest>
|
||||
<UseTestRunConfigFromMetadataFile>False</UseTestRunConfigFromMetadataFile>
|
||||
</UnitTestRunnerMSTest>
|
||||
</Configuration>
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual C# Express 2010
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpServer", "HttpServer\HttpServer.csproj", "{455E7D70-1C85-4D7F-9F01-DC801B8B8C34}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Test|Any CPU = Test|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{455E7D70-1C85-4D7F-9F01-DC801B8B8C34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{455E7D70-1C85-4D7F-9F01-DC801B8B8C34}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{455E7D70-1C85-4D7F-9F01-DC801B8B8C34}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{455E7D70-1C85-4D7F-9F01-DC801B8B8C34}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{455E7D70-1C85-4D7F-9F01-DC801B8B8C34}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||
{455E7D70-1C85-4D7F-9F01-DC801B8B8C34}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Binary file not shown.
@@ -1,120 +0,0 @@
|
||||
using System;
|
||||
using HttpServer.Exceptions;
|
||||
|
||||
namespace HttpServer.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate used to let authentication modules authenticate the user name and password.
|
||||
/// </summary>
|
||||
/// <param name="realm">Realm that the user want to authenticate in</param>
|
||||
/// <param name="userName">User name specified by client</param>
|
||||
/// <param name="token">Can either be user password or implementation specific token.</param>
|
||||
/// <param name="login">object that will be stored in a session variable called <see cref="AuthenticationModule.AuthenticationTag"/> if authentication was successful.</param>
|
||||
/// <exception cref="ForbiddenException">throw forbidden exception if too many attempts have been made.</exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Use <see cref="DigestAuthentication.TokenIsHA1"/> to specify that the token is a HA1 token. (MD5 generated
|
||||
/// string from realm, user name and password); Md5String(userName + ":" + realm + ":" + password);
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public delegate void AuthenticationHandler(string realm, string userName, ref string token, out object login);
|
||||
|
||||
/// <summary>
|
||||
/// Let's you decide on a system level if authentication is required.
|
||||
/// </summary>
|
||||
/// <param name="request">HTTP request from client</param>
|
||||
/// <returns>true if user should be authenticated.</returns>
|
||||
/// <remarks>throw <see cref="ForbiddenException"/> if no more attempts are allowed.</remarks>
|
||||
/// <exception cref="ForbiddenException">If no more attempts are allowed</exception>
|
||||
public delegate bool AuthenticationRequiredHandler(IHttpRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Authentication modules are used to implement different
|
||||
/// kind of HTTP authentication.
|
||||
/// </summary>
|
||||
public abstract class AuthenticationModule
|
||||
{
|
||||
private readonly AuthenticationHandler _authenticator;
|
||||
private readonly AuthenticationRequiredHandler _authenticationRequiredHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthenticationModule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="authenticator">Delegate used to provide information used during authentication.</param>
|
||||
/// <param name="authenticationRequiredHandler">Delegate used to determine if authentication is required (may be null).</param>
|
||||
protected AuthenticationModule(AuthenticationHandler authenticator, AuthenticationRequiredHandler authenticationRequiredHandler)
|
||||
{
|
||||
Check.Require(authenticator, "authenticator");
|
||||
_authenticationRequiredHandler = authenticationRequiredHandler;
|
||||
_authenticator = authenticator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthenticationModule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="authenticator">Delegate used to provide information used during authentication.</param>
|
||||
protected AuthenticationModule(AuthenticationHandler authenticator) : this(authenticator, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// name used in HTTP request.
|
||||
/// </summary>
|
||||
public abstract string Name
|
||||
{ get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tag used for authentication.
|
||||
/// </summary>
|
||||
public const string AuthenticationTag = "__authtag";
|
||||
|
||||
/// <summary>
|
||||
/// Create a response that can be sent in the WWW-Authenticate header.
|
||||
/// </summary>
|
||||
/// <param name="realm">Realm that the user should authenticate in</param>
|
||||
/// <param name="options">Array with optional options.</param>
|
||||
/// <returns>A correct authentication request.</returns>
|
||||
/// <exception cref="ArgumentNullException">If realm is empty or null.</exception>
|
||||
public abstract string CreateResponse(string realm, params object[] options);
|
||||
|
||||
/// <summary>
|
||||
/// An authentication response have been received from the web browser.
|
||||
/// Check if it's correct
|
||||
/// </summary>
|
||||
/// <param name="authenticationHeader">Contents from the Authorization header</param>
|
||||
/// <param name="realm">Realm that should be authenticated</param>
|
||||
/// <param name="httpVerb">GET/POST/PUT/DELETE etc.</param>
|
||||
/// <param name="options">options to specific implementations</param>
|
||||
/// <returns>Authentication object that is stored for the request. A user class or something like that.</returns>
|
||||
/// <exception cref="ArgumentException">if <paramref name="authenticationHeader"/> is invalid</exception>
|
||||
/// <exception cref="ArgumentNullException">If any of the parameters is empty or null.</exception>
|
||||
public abstract object Authenticate(string authenticationHeader, string realm, string httpVerb,
|
||||
params object[] options);
|
||||
|
||||
/// <summary>
|
||||
/// Used to invoke the authentication delegate that is used to lookup the user name/realm.
|
||||
/// </summary>
|
||||
/// <param name="realm">Realm (domain) that user want to authenticate in</param>
|
||||
/// <param name="userName">User name</param>
|
||||
/// <param name="password">Password used for validation. Some implementations got password in clear text, they are then sent to client.</param>
|
||||
/// <param name="login">object that will be stored in the request to help you identify the user if authentication was successful.</param>
|
||||
/// <returns>true if authentication was successful</returns>
|
||||
protected bool CheckAuthentication(string realm, string userName, ref string password, out object login)
|
||||
{
|
||||
_authenticator(realm, userName, ref password, out login);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if authentication is required.
|
||||
/// </summary>
|
||||
/// <param name="request">HTTP request from browser</param>
|
||||
/// <returns>true if user should be authenticated.</returns>
|
||||
/// <remarks>throw <see cref="ForbiddenException"/> from your delegate if no more attempts are allowed.</remarks>
|
||||
/// <exception cref="ForbiddenException">If no more attempts are allowed</exception>
|
||||
public bool AuthenticationRequired(IHttpRequest request)
|
||||
{
|
||||
return _authenticationRequiredHandler != null && _authenticationRequiredHandler(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// The "basic" authentication scheme is based on the model that the
|
||||
/// client must authenticate itself with a user-ID and a password for
|
||||
/// each realm. The realm value should be considered an opaque string
|
||||
/// which can only be compared for equality with other realms on that
|
||||
/// server. The server will service the request only if it can validate
|
||||
/// the user-ID and password for the protection space of the Request-URI.
|
||||
/// There are no optional authentication parameters.
|
||||
/// </summary>
|
||||
public class BasicAuthentication : AuthenticationModule
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BasicAuthentication"/> class.
|
||||
/// </summary>
|
||||
/// <param name="authenticator">Delegate used to provide information used during authentication.</param>
|
||||
/// <param name="authenticationRequiredHandler">Delegate used to determine if authentication is required (may be null).</param>
|
||||
public BasicAuthentication(AuthenticationHandler authenticator, AuthenticationRequiredHandler authenticationRequiredHandler)
|
||||
: base(authenticator, authenticationRequiredHandler)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BasicAuthentication"/> class.
|
||||
/// </summary>
|
||||
/// <param name="authenticator">Delegate used to provide information used during authentication.</param>
|
||||
public BasicAuthentication(AuthenticationHandler authenticator)
|
||||
: base(authenticator)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a response that can be sent in the WWW-Authenticate header.
|
||||
/// </summary>
|
||||
/// <param name="realm">Realm that the user should authenticate in</param>
|
||||
/// <param name="options">Not used in basic auth</param>
|
||||
/// <returns>A correct auth request.</returns>
|
||||
public override string CreateResponse(string realm, object[] options)
|
||||
{
|
||||
if (string.IsNullOrEmpty(realm))
|
||||
throw new ArgumentNullException("realm");
|
||||
|
||||
return "Basic realm=\"" + realm + "\"";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An authentication response have been received from the web browser.
|
||||
/// Check if it's correct
|
||||
/// </summary>
|
||||
/// <param name="authenticationHeader">Contents from the Authorization header</param>
|
||||
/// <param name="realm">Realm that should be authenticated</param>
|
||||
/// <param name="httpVerb">GET/POST/PUT/DELETE etc.</param>
|
||||
/// <param name="options">Not used in basic auth</param>
|
||||
/// <returns>Authentication object that is stored for the request. A user class or something like that.</returns>
|
||||
/// <exception cref="ArgumentException">if authenticationHeader is invalid</exception>
|
||||
/// <exception cref="ArgumentNullException">If any of the paramters is empty or null.</exception>
|
||||
public override object Authenticate(string authenticationHeader, string realm, string httpVerb, object[] options)
|
||||
{
|
||||
if (string.IsNullOrEmpty(authenticationHeader))
|
||||
throw new ArgumentNullException("realm");
|
||||
if (string.IsNullOrEmpty(realm))
|
||||
throw new ArgumentNullException("realm");
|
||||
if (string.IsNullOrEmpty(httpVerb))
|
||||
throw new ArgumentNullException("httpVerb");
|
||||
|
||||
/*
|
||||
* To receive authorization, the client sends the userid and password,
|
||||
separated by a single colon (":") character, within a base64 [7]
|
||||
encoded string in the credentials.*/
|
||||
authenticationHeader = authenticationHeader.Remove(0, 6);
|
||||
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationHeader));
|
||||
int pos = decoded.IndexOf(':');
|
||||
if (pos == -1)
|
||||
return null;
|
||||
|
||||
string ourPw = decoded.Substring(pos + 1, decoded.Length - pos - 1);
|
||||
string pw = ourPw;
|
||||
object state;
|
||||
CheckAuthentication(realm, decoded.Substring(0, pos), ref pw, out state);
|
||||
|
||||
return ourPw == pw ? state : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// name used in http request.
|
||||
/// </summary>
|
||||
public override string Name
|
||||
{
|
||||
get { return "basic"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace HttpServer.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements HTTP Digest authentication. It's more secure than Basic auth since password is
|
||||
/// encrypted with a "key" from the server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Keep in mind that the password is encrypted with MD5. Use a combination of SSL and digest auth to be secure.
|
||||
/// </remarks>
|
||||
public class DigestAuthentication : AuthenticationModule
|
||||
{
|
||||
static readonly Dictionary<string, DateTime> _nonces = new Dictionary<string, DateTime>();
|
||||
private static Timer _timer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DigestAuthentication"/> class.
|
||||
/// </summary>
|
||||
/// <param name="authenticator">Delegate used to provide information used during authentication.</param>
|
||||
/// <param name="authenticationRequiredHandler">Delegate used to determine if authentication is required (may be null).</param>
|
||||
public DigestAuthentication(AuthenticationHandler authenticator, AuthenticationRequiredHandler authenticationRequiredHandler)
|
||||
: base(authenticator, authenticationRequiredHandler)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DigestAuthentication"/> class.
|
||||
/// </summary>
|
||||
/// <param name="authenticator">Delegate used to provide information used during authentication.</param>
|
||||
public DigestAuthentication(AuthenticationHandler authenticator)
|
||||
: base(authenticator)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by test classes to be able to use hardcoded values
|
||||
/// </summary>
|
||||
public static bool DisableNonceCheck;
|
||||
|
||||
/// <summary>
|
||||
/// name used in http request.
|
||||
/// </summary>
|
||||
public override string Name
|
||||
{
|
||||
get { return "digest"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An authentication response have been received from the web browser.
|
||||
/// Check if it's correct
|
||||
/// </summary>
|
||||
/// <param name="authenticationHeader">Contents from the Authorization header</param>
|
||||
/// <param name="realm">Realm that should be authenticated</param>
|
||||
/// <param name="httpVerb">GET/POST/PUT/DELETE etc.</param>
|
||||
/// <param name="options">First option: true if username/password is correct but not cnonce</param>
|
||||
/// <returns>
|
||||
/// Authentication object that is stored for the request. A user class or something like that.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException">if authenticationHeader is invalid</exception>
|
||||
/// <exception cref="ArgumentNullException">If any of the paramters is empty or null.</exception>
|
||||
public override object Authenticate(string authenticationHeader, string realm, string httpVerb, object[] options)
|
||||
{
|
||||
lock (_nonces)
|
||||
{
|
||||
if (_timer == null)
|
||||
_timer = new Timer(ManageNonces, null, 15000, 15000);
|
||||
}
|
||||
|
||||
if (!authenticationHeader.StartsWith("Digest", true, CultureInfo.CurrentCulture))
|
||||
return null;
|
||||
|
||||
bool staleNonce;
|
||||
if (options.Length > 0)
|
||||
staleNonce = (bool) options[0];
|
||||
else staleNonce = false;
|
||||
|
||||
NameValueCollection reqInfo = Decode(authenticationHeader, Encoding.UTF8);
|
||||
if (!IsValidNonce(reqInfo["nonce"]) && !DisableNonceCheck)
|
||||
return null;
|
||||
|
||||
string username = reqInfo["username"];
|
||||
string password = string.Empty;
|
||||
object state;
|
||||
|
||||
if (!CheckAuthentication(realm, username, ref password, out state))
|
||||
return null;
|
||||
|
||||
string HA1;
|
||||
if (!TokenIsHA1)
|
||||
{
|
||||
string A1 = String.Format("{0}:{1}:{2}", username, realm, password);
|
||||
HA1 = GetMD5HashBinHex2(A1);
|
||||
}
|
||||
else
|
||||
HA1 = password;
|
||||
|
||||
string A2 = String.Format("{0}:{1}", httpVerb, reqInfo["uri"]);
|
||||
string HA2 = GetMD5HashBinHex2(A2);
|
||||
string hashedDigest = Encrypt(HA1, HA2, reqInfo["qop"],
|
||||
reqInfo["nonce"], reqInfo["nc"], reqInfo["cnonce"]);
|
||||
|
||||
if (reqInfo["response"] == hashedDigest && !staleNonce)
|
||||
return state;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the token supplied in <see cref="AuthenticationHandler"/> is a
|
||||
/// HA1 generated string.
|
||||
/// </summary>
|
||||
public bool TokenIsHA1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts parameters into a Digest string
|
||||
/// </summary>
|
||||
/// <param name="realm">Realm that the user want to log into.</param>
|
||||
/// <param name="userName">User logging in</param>
|
||||
/// <param name="password">Users password.</param>
|
||||
/// <param name="method">HTTP method.</param>
|
||||
/// <param name="uri">Uri/domain that generated the login prompt.</param>
|
||||
/// <param name="qop">Quality of Protection.</param>
|
||||
/// <param name="nonce">"Number used ONCE"</param>
|
||||
/// <param name="nc">Hexadecimal request counter.</param>
|
||||
/// <param name="cnonce">"Client Number used ONCE"</param>
|
||||
/// <returns>Digest encrypted string</returns>
|
||||
public static string Encrypt(string realm, string userName, string password, string method, string uri, string qop, string nonce, string nc, string cnonce)
|
||||
{
|
||||
string A1 = String.Format("{0}:{1}:{2}", userName, realm, password);
|
||||
string HA1 = GetMD5HashBinHex2(A1);
|
||||
string A2 = String.Format("{0}:{1}", method, uri);
|
||||
string HA2 = GetMD5HashBinHex2(A2);
|
||||
|
||||
string unhashedDigest;
|
||||
if (qop != null)
|
||||
{
|
||||
unhashedDigest = String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
|
||||
HA1,
|
||||
nonce,
|
||||
nc,
|
||||
cnonce,
|
||||
qop,
|
||||
HA2);
|
||||
}
|
||||
else
|
||||
{
|
||||
unhashedDigest = String.Format("{0}:{1}:{2}",
|
||||
HA1,
|
||||
nonce,
|
||||
HA2);
|
||||
}
|
||||
|
||||
return GetMD5HashBinHex2(unhashedDigest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ha1">Md5 hex encoded "userName:realm:password", without the quotes.</param>
|
||||
/// <param name="ha2">Md5 hex encoded "method:uri", without the quotes</param>
|
||||
/// <param name="qop">Quality of Protection</param>
|
||||
/// <param name="nonce">"Number used ONCE"</param>
|
||||
/// <param name="nc">Hexadecimal request counter.</param>
|
||||
/// <param name="cnonce">Client number used once</param>
|
||||
/// <returns></returns>
|
||||
protected virtual string Encrypt(string ha1, string ha2, string qop, string nonce, string nc, string cnonce)
|
||||
{
|
||||
string unhashedDigest;
|
||||
if (qop != null)
|
||||
{
|
||||
unhashedDigest = String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
|
||||
ha1,
|
||||
nonce,
|
||||
nc,
|
||||
cnonce,
|
||||
qop,
|
||||
ha2);
|
||||
}
|
||||
else
|
||||
{
|
||||
unhashedDigest = String.Format("{0}:{1}:{2}",
|
||||
ha1,
|
||||
nonce,
|
||||
ha2);
|
||||
}
|
||||
|
||||
return GetMD5HashBinHex2(unhashedDigest);
|
||||
}
|
||||
private static void ManageNonces(object state)
|
||||
{
|
||||
lock (_nonces)
|
||||
{
|
||||
foreach (KeyValuePair<string, DateTime> pair in _nonces)
|
||||
{
|
||||
if (pair.Value >= DateTime.Now)
|
||||
continue;
|
||||
|
||||
_nonces.Remove(pair.Key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a response that can be sent in the WWW-Authenticate header.
|
||||
/// </summary>
|
||||
/// <param name="realm">Realm that the user should authenticate in</param>
|
||||
/// <param name="options">First options specifies if true if username/password is correct but not cnonce.</param>
|
||||
/// <returns>A correct auth request.</returns>
|
||||
/// <exception cref="ArgumentNullException">If realm is empty or null.</exception>
|
||||
public override string CreateResponse(string realm, object[] options)
|
||||
{
|
||||
string nonce = GetCurrentNonce();
|
||||
|
||||
StringBuilder challenge = new StringBuilder("Digest realm=\"");
|
||||
challenge.Append(realm);
|
||||
challenge.Append("\"");
|
||||
challenge.Append(", nonce=\"");
|
||||
challenge.Append(nonce);
|
||||
challenge.Append("\"");
|
||||
challenge.Append(", opaque=\"" + Guid.NewGuid().ToString().Replace("-", string.Empty) + "\"");
|
||||
challenge.Append(", stale=");
|
||||
|
||||
if (options.Length > 0)
|
||||
challenge.Append((bool)options[0] ? "true" : "false");
|
||||
else
|
||||
challenge.Append("false");
|
||||
|
||||
challenge.Append(", algorithm=MD5");
|
||||
challenge.Append(", qop=auth");
|
||||
|
||||
return challenge.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes authorization header value
|
||||
/// </summary>
|
||||
/// <param name="buffer">header value</param>
|
||||
/// <param name="encoding">Encoding that the buffer is in</param>
|
||||
/// <returns>All headers and their values if successful; otherwise null</returns>
|
||||
/// <example>
|
||||
/// NameValueCollection header = DigestAuthentication.Decode("response=\"6629fae49393a05397450978507c4ef1\",\r\nc=00001", Encoding.ASCII);
|
||||
/// </example>
|
||||
/// <remarks>Can handle lots of whitespaces and new lines without failing.</remarks>
|
||||
public static NameValueCollection Decode(string buffer, Encoding encoding)
|
||||
{
|
||||
if (string.Compare(buffer.Substring(0, 7), "Digest ", true) == 0)
|
||||
buffer = buffer.Remove(0, 7).Trim(' ');
|
||||
|
||||
NameValueCollection values = new NameValueCollection();
|
||||
int step = 0;
|
||||
bool inQuote = false;
|
||||
string name = string.Empty;
|
||||
int start = 0;
|
||||
for (int i = start; i < buffer.Length; ++i)
|
||||
{
|
||||
char ch = buffer[i];
|
||||
if (ch == '"')
|
||||
inQuote = !inQuote;
|
||||
|
||||
//find start of name
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
if (!char.IsWhiteSpace(ch))
|
||||
{
|
||||
if (!char.IsLetterOrDigit(ch) && ch != '"')
|
||||
return null;
|
||||
start = i;
|
||||
++step;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (char.IsWhiteSpace(ch) || ch == '=')
|
||||
{
|
||||
if (start == -1)
|
||||
return null;
|
||||
name = buffer.Substring(start, i - start);
|
||||
start = -1;
|
||||
++step;
|
||||
}
|
||||
else if (!char.IsLetterOrDigit(ch) && ch != '"')
|
||||
return null;
|
||||
break;
|
||||
case 2:
|
||||
if (!char.IsWhiteSpace(ch) && ch != '=')
|
||||
{
|
||||
start = i;
|
||||
++step;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// find end of value
|
||||
if (step == 3)
|
||||
{
|
||||
if (inQuote)
|
||||
continue;
|
||||
|
||||
if (ch == ',' || char.IsWhiteSpace(ch) || i == buffer.Length -1)
|
||||
{
|
||||
if (start == -1)
|
||||
return null;
|
||||
|
||||
int stop = i;
|
||||
if (buffer[start] == '"')
|
||||
{
|
||||
++start;
|
||||
--stop;
|
||||
}
|
||||
if (i == buffer.Length - 1 || (i == buffer.Length - 2 && buffer[buffer.Length - 1] == '"'))
|
||||
++stop;
|
||||
|
||||
values.Add(name.ToLower(), buffer.Substring(start, stop - start));
|
||||
name = string.Empty;
|
||||
start = -1;
|
||||
step = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values.Count == 0 ? null : values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current nonce.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual string GetCurrentNonce()
|
||||
{
|
||||
string nonce = Guid.NewGuid().ToString().Replace("-", string.Empty);
|
||||
lock (_nonces)
|
||||
_nonces.Add(nonce, DateTime.Now.AddSeconds(30));
|
||||
|
||||
return nonce;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Md5 hash bin hex2.
|
||||
/// </summary>
|
||||
/// <param name="toBeHashed">To be hashed.</param>
|
||||
/// <returns></returns>
|
||||
public static string GetMD5HashBinHex2(string toBeHashed)
|
||||
{
|
||||
MD5 md5 = new MD5CryptoServiceProvider();
|
||||
byte[] result = md5.ComputeHash(Encoding.ASCII.GetBytes(toBeHashed));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (byte b in result)
|
||||
sb.Append(b.ToString("x2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// determines if the nonce is valid or has expired.
|
||||
/// </summary>
|
||||
/// <param name="nonce">nonce value (check wikipedia for info)</param>
|
||||
/// <returns>true if the nonce has not expired.</returns>
|
||||
protected virtual bool IsValidNonce(string nonce)
|
||||
{
|
||||
lock (_nonces)
|
||||
{
|
||||
if (_nonces.ContainsKey(nonce))
|
||||
{
|
||||
if (_nonces[nonce] < DateTime.Now)
|
||||
{
|
||||
_nonces.Remove(nonce);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Small design by contract implementation.
|
||||
/// </summary>
|
||||
public static class Check
|
||||
{
|
||||
/// <summary>
|
||||
/// Check whether a parameter is empty.
|
||||
/// </summary>
|
||||
/// <param name="value">Parameter value</param>
|
||||
/// <param name="parameterOrErrorMessage">Parameter name, or error description.</param>
|
||||
/// <exception cref="ArgumentException">value is empty.</exception>
|
||||
public static void NotEmpty(string value, string parameterOrErrorMessage)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
return;
|
||||
|
||||
if (parameterOrErrorMessage.IndexOf(' ') == -1)
|
||||
throw new ArgumentException("'" + parameterOrErrorMessage + "' cannot be empty.", parameterOrErrorMessage);
|
||||
|
||||
throw new ArgumentException(parameterOrErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a parameter is null.
|
||||
/// </summary>
|
||||
/// <param name="value">Parameter value</param>
|
||||
/// <param name="parameterOrErrorMessage">Parameter name, or error description.</param>
|
||||
/// <exception cref="ArgumentNullException">value is null.</exception>
|
||||
public static void Require(object value, string parameterOrErrorMessage)
|
||||
{
|
||||
if (value != null)
|
||||
return;
|
||||
|
||||
if (parameterOrErrorMessage.IndexOf(' ') == -1)
|
||||
throw new ArgumentNullException("'" + parameterOrErrorMessage + "' cannot be null.", parameterOrErrorMessage);
|
||||
|
||||
throw new ArgumentNullException(parameterOrErrorMessage);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a parameter is null.
|
||||
/// </summary>
|
||||
/// <param name="minValue"></param>
|
||||
/// <param name="value">Parameter value</param>
|
||||
/// <param name="parameterOrErrorMessage">Parameter name, or error description.</param>
|
||||
/// <exception cref="ArgumentException">value is null.</exception>
|
||||
public static void Min(int minValue, object value, string parameterOrErrorMessage)
|
||||
{
|
||||
if (value != null)
|
||||
return;
|
||||
|
||||
if (parameterOrErrorMessage.IndexOf(' ') == -1)
|
||||
throw new ArgumentException("'" + parameterOrErrorMessage + "' must be at least " + minValue + ".", parameterOrErrorMessage);
|
||||
|
||||
throw new ArgumentException(parameterOrErrorMessage);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when a client have been accepted by the <see cref="HttpListener"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can be used to revoke incoming connections
|
||||
/// </remarks>
|
||||
public class ClientAcceptedEventArgs : EventArgs
|
||||
{
|
||||
private readonly Socket _socket;
|
||||
private bool _revoke;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClientAcceptedEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="socket">The socket.</param>
|
||||
public ClientAcceptedEventArgs(Socket socket)
|
||||
{
|
||||
_socket = socket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepted socket.
|
||||
/// </summary>
|
||||
public Socket Socket
|
||||
{
|
||||
get { return _socket; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client should be revoked.
|
||||
/// </summary>
|
||||
public bool Revoked
|
||||
{
|
||||
get { return _revoke; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client may not be handled.
|
||||
/// </summary>
|
||||
public void Revoke()
|
||||
{
|
||||
_revoke = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
class ComponentProvider : IComponentProvider
|
||||
{
|
||||
private readonly Dictionary<Type, TypeInformation> _instances = new Dictionary<Type, TypeInformation>();
|
||||
|
||||
/// <summary>
|
||||
/// Add a component instance
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Interface type</typeparam>
|
||||
/// <param name="instance">Instance to add</param>
|
||||
public void AddInstance<T>(object instance)
|
||||
{
|
||||
Type interfaceType = typeof (T);
|
||||
lock (_instances)
|
||||
_instances.Add(interfaceType,
|
||||
new TypeInformation
|
||||
{
|
||||
InstanceType = instance.GetType(),
|
||||
Instance = instance,
|
||||
InterfaceType = interfaceType
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a component.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Interface type</typeparam>
|
||||
/// <returns>Component if registered, otherwise null.</returns>
|
||||
/// <remarks>
|
||||
/// Component will get created if needed.
|
||||
/// </remarks>
|
||||
public T Get<T>() where T : class
|
||||
{
|
||||
Type type = typeof (T);
|
||||
TypeInformation typeInformation;
|
||||
lock (_instances)
|
||||
if (!_instances.TryGetValue(type, out typeInformation))
|
||||
return null;
|
||||
|
||||
if (typeInformation.Instance != null)
|
||||
return (T)typeInformation.Instance;
|
||||
|
||||
return (T)Create(typeInformation);
|
||||
}
|
||||
|
||||
private class ConstructorParameter
|
||||
{
|
||||
public object Instance { get; set; }
|
||||
public ParameterInfo Parameter { get; set; }
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidOperationException">If instance cannot be created.</exception>
|
||||
private object Create(TypeInformation information)
|
||||
{
|
||||
Dictionary<ConstructorInfo, List<ConstructorParameter>> constructors = new Dictionary<ConstructorInfo, List<ConstructorParameter>>();
|
||||
ConstructorInfo[] publicConstructors = information.InstanceType.GetConstructors();
|
||||
if (publicConstructors.Length == 0)
|
||||
throw new InvalidOperationException(information.InstanceType.FullName + " do not have any public constructors.");
|
||||
|
||||
foreach (var constructor in publicConstructors)
|
||||
{
|
||||
constructors.Add(constructor, new List<ConstructorParameter>());
|
||||
foreach (var parameter in constructor.GetParameters())
|
||||
{
|
||||
ConstructorParameter constructorParameter = new ConstructorParameter {Parameter = parameter};
|
||||
constructors[constructor].Add(constructorParameter);
|
||||
|
||||
TypeInformation typeInfo;
|
||||
lock (_instances)
|
||||
if (!_instances.TryGetValue(parameter.ParameterType, out typeInfo))
|
||||
continue; // this constructor wont work, but check what parameters we are missing.
|
||||
|
||||
try
|
||||
{
|
||||
constructorParameter.Instance = typeInfo.Instance ?? Create(typeInfo);
|
||||
}
|
||||
catch(InvalidOperationException err)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Failed to create '" + typeInfo.InterfaceType.FullName + "' that '" + information.InterfaceType +
|
||||
"' is dependent off. Check inner exception for more details.", err);
|
||||
}
|
||||
}
|
||||
|
||||
// check if all parameters was found.
|
||||
bool allFound = true;
|
||||
foreach (var parameter in constructors[constructor])
|
||||
{
|
||||
if (parameter.Instance != null) continue;
|
||||
allFound = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!allFound)
|
||||
continue;
|
||||
|
||||
// now create instance.
|
||||
information.ConstructorArguments = new object[constructors[constructor].Count];
|
||||
int index = 0;
|
||||
foreach (var parameter in constructors[constructor])
|
||||
information.ConstructorArguments[index++] = parameter.Instance;
|
||||
return Activator.CreateInstance(information.InstanceType, information.ConstructorArguments);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("Failed to create '" + information.InstanceType.FullName + "', due to missing constructorparamters.");
|
||||
foreach (var pair in constructors)
|
||||
{
|
||||
sb.Append(pair.Key + " are missing: ");
|
||||
foreach (var parameter in pair.Value)
|
||||
{
|
||||
if (parameter.Instance == null)
|
||||
sb.Append(parameter.Parameter.Name + ", ");
|
||||
}
|
||||
sb.Length -= 2;
|
||||
sb.AppendLine();
|
||||
}
|
||||
throw new InvalidOperationException(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified component interface have been added.
|
||||
/// </summary>
|
||||
/// <param name="interfaceType"></param>
|
||||
/// <returns>true if found; otherwise false.</returns>
|
||||
public bool Contains(Type interfaceType)
|
||||
{
|
||||
return _instances.ContainsKey(interfaceType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a component.
|
||||
/// </summary>
|
||||
/// <typeparam name="InterfaceType">Type being requested.</typeparam>
|
||||
/// <typeparam name="InstanceType">Type being created.</typeparam>
|
||||
/// <exception cref="InvalidOperationException">Type have already been mapped.</exception>
|
||||
public void Add<InterfaceType, InstanceType>()
|
||||
{
|
||||
Type interfaceType = typeof (InterfaceType);
|
||||
TypeInformation typeInformation = new TypeInformation()
|
||||
{
|
||||
InstanceType = typeof (InstanceType),
|
||||
InterfaceType = interfaceType,
|
||||
Instance = null
|
||||
};
|
||||
lock (_instances)
|
||||
{
|
||||
if (_instances.ContainsKey(interfaceType))
|
||||
throw new InvalidOperationException("Type '" + interfaceType + "' have already been mapped to '" +
|
||||
typeof (InstanceType).FullName);
|
||||
_instances.Add(interfaceType, typeInformation);
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeInformation
|
||||
{
|
||||
public Type InterfaceType { get; set; }
|
||||
public Type InstanceType { get; set; }
|
||||
public object Instance { get; set; }
|
||||
public object[] ConstructorArguments { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Lists content type mime types.
|
||||
/// </summary>
|
||||
public class ContentType
|
||||
{
|
||||
/// <summary>
|
||||
/// text/plain
|
||||
/// </summary>
|
||||
public const string Text = "text/plain";
|
||||
|
||||
/// <summary>
|
||||
/// text/haml
|
||||
/// </summary>
|
||||
public const string Html = "text/html";
|
||||
|
||||
/// <summary>
|
||||
/// content type for javascript documents = application/javascript
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// RFC 4329 states that text/javascript have been superseeded by
|
||||
/// application/javascript. You might still want to check browser versions
|
||||
/// since older ones do not support application/javascript.
|
||||
/// </para>
|
||||
/// <para>Browser support: http://krijnhoetmer.nl/stuff/javascript/mime-types/</para>
|
||||
/// </remarks>
|
||||
public const string Javascript = "application/javascript";
|
||||
|
||||
/// <summary>
|
||||
/// text/xml
|
||||
/// </summary>
|
||||
public const string Xml = "text/xml";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of content types
|
||||
/// </summary>
|
||||
public class ContentTypes : IEnumerable
|
||||
{
|
||||
private readonly string[] _contentTypes;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="types">Semicolon separated content types.</param>
|
||||
public ContentTypes(string types)
|
||||
{
|
||||
if (types == null)
|
||||
_contentTypes = new string[] { ContentType.Html };
|
||||
else
|
||||
_contentTypes = types.Split(';');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get this first content type.
|
||||
/// </summary>
|
||||
public string First
|
||||
{
|
||||
get { return _contentTypes.Length == 0 ? string.Empty : _contentTypes[0]; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch a content type
|
||||
/// </summary>
|
||||
/// <param name="type">Part of type ("xml" would return "application/xml")</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>All content types are in lower case.</remarks>
|
||||
public string this[string type]
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (string contentType in _contentTypes)
|
||||
{
|
||||
if (contentType.Contains(type))
|
||||
return contentType;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through a collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return _contentTypes.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the specified type
|
||||
/// </summary>
|
||||
/// <param name="type">Can also be a part of a type (searching for "xml" would return true for "application/xml").</param>
|
||||
/// <returns>true if type was found.</returns>
|
||||
public bool Contains(string type)
|
||||
{
|
||||
foreach (string contentType in _contentTypes)
|
||||
{
|
||||
if (contentType.Contains(type))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,413 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSimulator Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Timeout Manager. Checks for dead clients. Clients with open connections that are not doing anything. Closes sessions opened with keepalive.
|
||||
/// </summary>
|
||||
public static class ContextTimeoutManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Use a Thread or a Timer to monitor the ugly
|
||||
/// </summary>
|
||||
private static Thread m_internalThread = null;
|
||||
private static object m_threadLock = new object();
|
||||
private static ConcurrentQueue<HttpClientContext> m_contexts = new ConcurrentQueue<HttpClientContext>();
|
||||
private static ConcurrentQueue<HttpClientContext> m_highPrio = new ConcurrentQueue<HttpClientContext>();
|
||||
private static ConcurrentQueue<HttpClientContext> m_midPrio = new ConcurrentQueue<HttpClientContext>();
|
||||
private static ConcurrentQueue<HttpClientContext> m_lowPrio = new ConcurrentQueue<HttpClientContext>();
|
||||
private static AutoResetEvent m_processWaitEven = new AutoResetEvent(false);
|
||||
private static bool m_shuttingDown;
|
||||
|
||||
private static int m_ActiveSendingCount;
|
||||
private static double m_lastTimeOutCheckTime = 0;
|
||||
private static double m_lastSendCheckTime = 0;
|
||||
|
||||
const int m_maxBandWidth = 10485760; //80Mbps
|
||||
const int m_maxConcurrenSend = 32;
|
||||
|
||||
static ContextTimeoutManager()
|
||||
{
|
||||
TimeStampClockPeriod = 1.0 / (double)Stopwatch.Frequency;
|
||||
TimeStampClockPeriodMS = 1e3 / (double)Stopwatch.Frequency;
|
||||
}
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
lock (m_threadLock)
|
||||
{
|
||||
if (m_internalThread != null)
|
||||
return;
|
||||
|
||||
m_lastTimeOutCheckTime = GetTimeStampMS();
|
||||
m_internalThread = new Thread(ThreadRunProcess);
|
||||
m_internalThread.Priority = ThreadPriority.Normal;
|
||||
m_internalThread.IsBackground = true;
|
||||
m_internalThread.CurrentCulture = new CultureInfo("en-US", false);
|
||||
m_internalThread.Name = "HttpServerMain";
|
||||
m_internalThread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
m_shuttingDown = true;
|
||||
m_internalThread.Join();
|
||||
ProcessShutDown();
|
||||
}
|
||||
|
||||
private static void ThreadRunProcess()
|
||||
{
|
||||
while (!m_shuttingDown)
|
||||
{
|
||||
m_processWaitEven.WaitOne(100);
|
||||
|
||||
if(m_shuttingDown)
|
||||
return;
|
||||
|
||||
double now = GetTimeStampMS();
|
||||
if(m_contexts.Count > 0)
|
||||
{
|
||||
ProcessSendQueues(now);
|
||||
|
||||
if (now - m_lastTimeOutCheckTime > 1000)
|
||||
{
|
||||
ProcessContextTimeouts();
|
||||
m_lastTimeOutCheckTime = now;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_lastTimeOutCheckTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ProcessShutDown()
|
||||
{
|
||||
try
|
||||
{
|
||||
SocketError disconnectError = SocketError.HostDown;
|
||||
for (int i = 0; i < m_contexts.Count; i++)
|
||||
{
|
||||
HttpClientContext context = null;
|
||||
if (m_contexts.TryDequeue(out context))
|
||||
{
|
||||
try
|
||||
{
|
||||
context.Disconnect(disconnectError);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
m_processWaitEven.Dispose();
|
||||
m_processWaitEven = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We can't let this crash.
|
||||
}
|
||||
}
|
||||
|
||||
public static void ProcessSendQueues(double now)
|
||||
{
|
||||
int inqueues = m_highPrio.Count + m_midPrio.Count + m_lowPrio.Count;
|
||||
if(inqueues == 0)
|
||||
return;
|
||||
|
||||
double dt = now - m_lastSendCheckTime;
|
||||
m_lastSendCheckTime = now;
|
||||
|
||||
int totalSending = m_ActiveSendingCount;
|
||||
|
||||
int curConcurrentLimit = m_maxConcurrenSend - totalSending;
|
||||
if(curConcurrentLimit <= 0)
|
||||
return;
|
||||
|
||||
if(curConcurrentLimit > inqueues)
|
||||
curConcurrentLimit = inqueues;
|
||||
|
||||
if (dt > 0.1)
|
||||
dt = 0.1;
|
||||
|
||||
dt /= curConcurrentLimit;
|
||||
int curbytesLimit = (int)(m_maxBandWidth * dt);
|
||||
if(curbytesLimit < 8192)
|
||||
curbytesLimit = 8192;
|
||||
|
||||
HttpClientContext ctx;
|
||||
int sent;
|
||||
while (curConcurrentLimit > 0)
|
||||
{
|
||||
sent = 0;
|
||||
while (m_highPrio.TryDequeue(out ctx))
|
||||
{
|
||||
if(TrySend(ctx, curbytesLimit))
|
||||
m_highPrio.Enqueue(ctx);
|
||||
|
||||
if (m_shuttingDown)
|
||||
return;
|
||||
--curConcurrentLimit;
|
||||
if (++sent == 4)
|
||||
break;
|
||||
}
|
||||
|
||||
sent = 0;
|
||||
while(m_midPrio.TryDequeue(out ctx))
|
||||
{
|
||||
if(TrySend(ctx, curbytesLimit))
|
||||
m_midPrio.Enqueue(ctx);
|
||||
|
||||
if (m_shuttingDown)
|
||||
return;
|
||||
--curConcurrentLimit;
|
||||
if (++sent >= 2)
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_lowPrio.TryDequeue(out ctx))
|
||||
{
|
||||
--curConcurrentLimit;
|
||||
if(TrySend(ctx, curbytesLimit))
|
||||
m_lowPrio.Enqueue(ctx);
|
||||
}
|
||||
|
||||
if (m_shuttingDown)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TrySend(HttpClientContext ctx, int bytesLimit)
|
||||
{
|
||||
if(!ctx.CanSend())
|
||||
return false;
|
||||
|
||||
return ctx.TrySendResponse(bytesLimit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes the watcher to immediately check the connections.
|
||||
/// </summary>
|
||||
public static void ProcessContextTimeouts()
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < m_contexts.Count; i++)
|
||||
{
|
||||
if (m_shuttingDown)
|
||||
return;
|
||||
if (m_contexts.TryDequeue(out HttpClientContext context))
|
||||
{
|
||||
if (!ContextTimedOut(context, out SocketError disconnectError))
|
||||
m_contexts.Enqueue(context);
|
||||
else if(disconnectError != SocketError.InProgress)
|
||||
context.Disconnect(disconnectError);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We can't let this crash.
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ContextTimedOut(HttpClientContext context, out SocketError disconnectError)
|
||||
{
|
||||
disconnectError = SocketError.InProgress;
|
||||
|
||||
// First our error conditions
|
||||
if (context.contextID < 0 || context.StopMonitoring || context.StreamPassedOff)
|
||||
return true;
|
||||
|
||||
// Now we start checking for actual timeouts
|
||||
|
||||
// First we check that we got at least one line within context.TimeoutFirstLine milliseconds
|
||||
if (!context.FirstRequestLineReceived)
|
||||
{
|
||||
if (EnvironmentTickCountAdd(context.TimeoutFirstLine, context.MonitorStartMS) <= EnvironmentTickCount())
|
||||
{
|
||||
disconnectError = SocketError.TimedOut;
|
||||
context.MonitorStartMS = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.FullRequestReceived)
|
||||
{
|
||||
if (EnvironmentTickCountAdd(context.TimeoutRequestReceived, context.MonitorStartMS) <= EnvironmentTickCount())
|
||||
{
|
||||
disconnectError = SocketError.TimedOut;
|
||||
context.MonitorStartMS = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (!context.FullRequestProcessed)
|
||||
{
|
||||
if (EnvironmentTickCountAdd(context.TimeoutFullRequestProcessed, context.MonitorStartMS) <= EnvironmentTickCount())
|
||||
{
|
||||
disconnectError = SocketError.TimedOut;
|
||||
context.MonitorStartMS = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.TriggerKeepalive)
|
||||
{
|
||||
context.TriggerKeepalive = false;
|
||||
context.MonitorKeepaliveMS = EnvironmentTickCount();
|
||||
}
|
||||
|
||||
if (context.FullRequestProcessed && context.MonitorKeepaliveMS == 0)
|
||||
return true;
|
||||
|
||||
if (context.MonitorKeepaliveMS != 0 &&
|
||||
EnvironmentTickCountAdd(context.TimeoutKeepAlive, context.MonitorKeepaliveMS) <= EnvironmentTickCount())
|
||||
{
|
||||
disconnectError = SocketError.TimedOut;
|
||||
context.MonitorStartMS = 0;
|
||||
context.MonitorKeepaliveMS = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void StartMonitoringContext(HttpClientContext context)
|
||||
{
|
||||
context.MonitorStartMS = EnvironmentTickCount();
|
||||
m_contexts.Enqueue(context);
|
||||
}
|
||||
|
||||
public static void EnqueueSend(HttpClientContext context, int priority)
|
||||
{
|
||||
switch(priority)
|
||||
{
|
||||
case 0:
|
||||
m_highPrio.Enqueue(context);
|
||||
break;
|
||||
case 1:
|
||||
m_midPrio.Enqueue(context);
|
||||
break;
|
||||
case 2:
|
||||
m_lowPrio.Enqueue(context);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
m_processWaitEven.Set();
|
||||
}
|
||||
|
||||
public static void ContextEnterActiveSend()
|
||||
{
|
||||
Interlocked.Increment(ref m_ActiveSendingCount);
|
||||
}
|
||||
|
||||
public static void ContextLeaveActiveSend()
|
||||
{
|
||||
Interlocked.Decrement(ref m_ActiveSendingCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Environment.TickCount is an int but it counts all 32 bits so it goes positive
|
||||
/// and negative every 24.9 days. This trims down TickCount so it doesn't wrap
|
||||
/// for the callers.
|
||||
/// This trims it to a 12 day interval so don't let your frame time get too long.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int EnvironmentTickCount()
|
||||
{
|
||||
return Environment.TickCount & EnvironmentTickCountMask;
|
||||
}
|
||||
const int EnvironmentTickCountMask = 0x3fffffff;
|
||||
|
||||
/// <summary>
|
||||
/// Environment.TickCount is an int but it counts all 32 bits so it goes positive
|
||||
/// and negative every 24.9 days. Subtracts the passed value (previously fetched by
|
||||
/// 'EnvironmentTickCount()') and accounts for any wrapping.
|
||||
/// </summary>
|
||||
/// <param name="newValue"></param>
|
||||
/// <param name="prevValue"></param>
|
||||
/// <returns>subtraction of passed prevValue from current Environment.TickCount</returns>
|
||||
public static int EnvironmentTickCountSubtract(Int32 newValue, Int32 prevValue)
|
||||
{
|
||||
int diff = newValue - prevValue;
|
||||
return (diff >= 0) ? diff : (diff + EnvironmentTickCountMask + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Environment.TickCount is an int but it counts all 32 bits so it goes positive
|
||||
/// and negative every 24.9 days. Subtracts the passed value (previously fetched by
|
||||
/// 'EnvironmentTickCount()') and accounts for any wrapping.
|
||||
/// </summary>
|
||||
/// <param name="newValue"></param>
|
||||
/// <param name="prevValue"></param>
|
||||
/// <returns>subtraction of passed prevValue from current Environment.TickCount</returns>
|
||||
public static int EnvironmentTickCountAdd(Int32 newValue, Int32 prevValue)
|
||||
{
|
||||
int ret = newValue + prevValue;
|
||||
return (ret >= 0) ? ret : (ret + EnvironmentTickCountMask + 1);
|
||||
}
|
||||
|
||||
public static double TimeStampClockPeriodMS;
|
||||
public static double TimeStampClockPeriod;
|
||||
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public static double GetTimeStamp()
|
||||
{
|
||||
return Stopwatch.GetTimestamp() * TimeStampClockPeriod;
|
||||
}
|
||||
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public static double GetTimeStampMS()
|
||||
{
|
||||
return Stopwatch.GetTimestamp() * TimeStampClockPeriodMS;
|
||||
}
|
||||
|
||||
// doing math in ticks is usefull to avoid loss of resolution
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public static long GetTimeStampTicks()
|
||||
{
|
||||
return Stopwatch.GetTimestamp();
|
||||
}
|
||||
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public static double TimeStampTicksToMS(long ticks)
|
||||
{
|
||||
return ticks * TimeStampClockPeriodMS;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks methods to let framework know that the method is protected
|
||||
/// </summary>
|
||||
/// <seealso cref="AuthValidatorAttribute"/>
|
||||
/// <seealso cref="RequestController"/>
|
||||
public class AuthRequiredAttribute : Attribute
|
||||
{
|
||||
private readonly int _level;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthRequiredAttribute"/> class.
|
||||
/// </summary>
|
||||
public AuthRequiredAttribute()
|
||||
{
|
||||
_level = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="level">
|
||||
/// Level is a value defined by you. It should be used to determine the users
|
||||
/// that can access the method tagged with the AuthRequired attribute.
|
||||
/// </param>
|
||||
/// <example>
|
||||
/// <![CDATA[
|
||||
/// public enum UserLevel
|
||||
/// {
|
||||
/// Guest,
|
||||
/// User,
|
||||
/// Admin,
|
||||
/// SuperAdmin
|
||||
/// }
|
||||
/// public class UserController : RequestController
|
||||
/// {
|
||||
/// [AuthRequired(UserLevel.User)]
|
||||
/// public string View()
|
||||
/// {
|
||||
/// return "Can also be viewed by users";
|
||||
/// }
|
||||
///
|
||||
/// [AuthValidatorAttribute]
|
||||
/// public bool ValidateUser(int level)
|
||||
/// {
|
||||
/// (User)user = Session["user"];
|
||||
/// return user != null && user.Status >= level;
|
||||
/// }
|
||||
/// }
|
||||
/// ]]>
|
||||
/// </example>
|
||||
public AuthRequiredAttribute(int level)
|
||||
{
|
||||
_level = level;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Level is a value defined by you. It should be used to determine the users
|
||||
/// that can access the method tagged with the AuthRequired attribute.
|
||||
/// </summary>
|
||||
public int Level
|
||||
{
|
||||
get { return _level; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using System;
|
||||
using HttpServer;
|
||||
using HttpServer.HttpModules;
|
||||
|
||||
namespace HttpServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Method marked with this attribute determines if authentication is required.
|
||||
/// </summary>
|
||||
/// <seealso cref="ControllerModule"/>
|
||||
/// <seealso cref="HttpServer"/>
|
||||
/// <seealso cref="AuthRequiredAttribute"/>
|
||||
/// <seealso cref="WebSiteModule"/>
|
||||
/// <remarks>
|
||||
/// <para>The method should take one parameter (int level), return a bool and be protected/private.</para>
|
||||
/// <para>You should throw UnauthorizedException if you are using HTTP authentication.</para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <![CDATA[
|
||||
/// public enum UserLevel
|
||||
/// {
|
||||
/// Guest,
|
||||
/// User,
|
||||
/// Admin,
|
||||
/// SuperAdmin
|
||||
/// }
|
||||
/// public class UserController : RequestController
|
||||
/// {
|
||||
/// [AuthRequired(UserLevel.User)]
|
||||
/// public string View()
|
||||
/// {
|
||||
/// return "Can also be viewed by users";
|
||||
/// }
|
||||
///
|
||||
/// [AuthValidatorAttribute]
|
||||
/// public bool ValidateUser(int level)
|
||||
/// {
|
||||
/// (User)user = Session["user"];
|
||||
/// return user != null && user.Status >= level;
|
||||
/// }
|
||||
/// }
|
||||
/// ]]>
|
||||
/// </example>
|
||||
public class AuthValidatorAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods marked with BeforeFilter will be invoked before each request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// BeforeFilters should take no arguments and return false
|
||||
/// if controller method should not be invoked.
|
||||
/// </remarks>
|
||||
/// <seealso cref="FilterPosition"/>
|
||||
public class BeforeFilterAttribute : Attribute
|
||||
{
|
||||
private readonly FilterPosition _position;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BeforeFilterAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// BeforeFilters should take no arguments and return false
|
||||
/// if controller method should not be invoked.
|
||||
/// </remarks>
|
||||
public BeforeFilterAttribute()
|
||||
{
|
||||
_position = FilterPosition.Between;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BeforeFilterAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="position">Specify if the filter should be invoked among the first filters, in between or among the last.</param>
|
||||
/// <remarks>
|
||||
/// BeforeFilters should take no arguments and return false
|
||||
/// if controller method should not be invoked.
|
||||
/// </remarks>
|
||||
public BeforeFilterAttribute(FilterPosition position)
|
||||
{
|
||||
_position = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters position in before filter queue
|
||||
/// </summary>
|
||||
public FilterPosition Position
|
||||
{
|
||||
get { return _position; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determins when a before filter is executed.
|
||||
/// </summary>
|
||||
/// <seealso cref="BeforeFilterAttribute"/>
|
||||
public enum FilterPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter will be invoked first (unless another filter is added after this one with the First position)
|
||||
/// </summary>
|
||||
First,
|
||||
/// <summary>
|
||||
/// Invoke after all first filters, and before the last filters.
|
||||
/// </summary>
|
||||
Between,
|
||||
/// <summary>
|
||||
/// Filter will be invoked last (unless another filter is added after this one with the Last position)
|
||||
/// </summary>
|
||||
Last
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// This attribute can be used to map a controller to a specific url without using
|
||||
/// the class name.
|
||||
/// </summary>
|
||||
public class ControllerNameAttribute : Attribute
|
||||
{
|
||||
private readonly string _name;
|
||||
|
||||
/// <summary>
|
||||
/// Maps a controller to a url without using the controller name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Per default the class name is used to determine which url to handle.
|
||||
/// For instance, "class UserController" or "class User" tells the framework that
|
||||
/// the urls that starts with "/user" should be handled by that controller.</para>
|
||||
/// <para>This attribute can be used to circumvent that.</para>
|
||||
/// </remarks>
|
||||
/// <param name="name">The name.</param>
|
||||
public ControllerNameAttribute(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentNullException("name");
|
||||
|
||||
_name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name that the controller should use
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.Sessions;
|
||||
using Xunit;
|
||||
|
||||
namespace HttpServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to simply testing of controls.
|
||||
/// </summary>
|
||||
public class ControllerTester
|
||||
{
|
||||
/// <summary>
|
||||
/// Fake host name, default is "http://localhost"
|
||||
/// </summary>
|
||||
public string HostName = "http://localhost";
|
||||
|
||||
private static readonly IHttpClientContext TestContext = new MyContext();
|
||||
|
||||
/// <summary>
|
||||
/// Session used if null have been specified as argument to one of the class methods.
|
||||
/// </summary>
|
||||
public IHttpSession DefaultSession = new MemorySession("abc");
|
||||
|
||||
/// <summary>
|
||||
/// Send a GET request to a controller.
|
||||
/// </summary>
|
||||
/// <param name="controller">Controller receiving the post request.</param>
|
||||
/// <param name="uri">Uri visited.</param>
|
||||
/// <param name="response">Response from the controller.</param>
|
||||
/// <param name="session">Session used during the test. null = <see cref="DefaultSession"/> is used.</param>
|
||||
/// <returns>body posted by the response object</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// void MyTest()
|
||||
/// {
|
||||
/// ControllerTester tester = new ControllerTester();
|
||||
///
|
||||
/// MyController controller = new MyController();
|
||||
/// IHttpResponse response;
|
||||
/// string text = Get(controller, "/my/hello/1?hello=world", out response, null);
|
||||
/// Assert.Equal("world|1", text);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public string Get(RequestController controller, string uri, out IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
return Invoke(controller, Method.Get, uri, out response, session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a POST request to a controller.
|
||||
/// </summary>
|
||||
/// <param name="controller">Controller receiving the post request.</param>
|
||||
/// <param name="uri">Uri visited.</param>
|
||||
/// <param name="form">Form being processed by controller.</param>
|
||||
/// <param name="response">Response from the controller.</param>
|
||||
/// <param name="session">Session used during the test. null = <see cref="DefaultSession"/> is used.</param>
|
||||
/// <returns>body posted by the response object</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// void MyTest()
|
||||
/// {
|
||||
/// // Create a controller.
|
||||
/// MyController controller = new MyController();
|
||||
///
|
||||
/// // build up a form that is used by the controller.
|
||||
/// HttpForm form = new HttpForm();
|
||||
/// form.Add("user[firstName]", "Jonas");
|
||||
///
|
||||
/// // Invoke the request
|
||||
/// ControllerTester tester = new ControllerTester();
|
||||
/// IHttpResponse response;
|
||||
/// string text = tester.Get(controller, "/user/create/", form, out response, null);
|
||||
///
|
||||
/// // validate response back from controller.
|
||||
/// Assert.Equal("User 'Jonas' has been created.", text);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public string Post(RequestController controller, string uri, HttpForm form, out IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
return Invoke(controller, Method.Post, uri, form, out response, session);
|
||||
}
|
||||
|
||||
|
||||
private string Invoke(RequestController controller, string httpMetod, string uri, out IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
return Invoke(controller, httpMetod, uri, null, out response, session);
|
||||
}
|
||||
|
||||
// ReSharper disable SuggestBaseTypeForParameter
|
||||
private string Invoke(RequestController controller, string httpMetod, string uri, HttpForm form, out IHttpResponse response, IHttpSession session)
|
||||
// ReSharper restore SuggestBaseTypeForParameter
|
||||
{
|
||||
HttpRequest request = new HttpRequest
|
||||
{
|
||||
HttpVersion = "HTTP/1.1",
|
||||
UriPath = uri,
|
||||
Method = httpMetod,
|
||||
Uri = new Uri(HostName + uri)
|
||||
};
|
||||
request.AssignForm(form);
|
||||
|
||||
response = request.CreateResponse(TestContext);
|
||||
if(!controller.Process(request, response, session))
|
||||
throw new NotFoundException("404 could not find processor for: " + uri);
|
||||
|
||||
response.Body.Seek(0, SeekOrigin.Begin);
|
||||
StreamReader reader = new StreamReader(response.Body);
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private void TestGet()
|
||||
{
|
||||
MyController controller = new MyController();
|
||||
IHttpResponse response;
|
||||
IHttpSession session = DefaultSession;
|
||||
string text = Get(controller, "/my/hello/1?hello=world", out response, session);
|
||||
Assert.Equal("world|1", text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private void TestPost()
|
||||
{
|
||||
MyController controller = new MyController();
|
||||
IHttpResponse response;
|
||||
IHttpSession session = DefaultSession;
|
||||
HttpForm form = new HttpForm();
|
||||
form.Add("user[firstName]", "jonas");
|
||||
string text = Post(controller, "/my/hello/id", form, out response, session);
|
||||
Assert.Equal("jonas", text);
|
||||
}
|
||||
|
||||
|
||||
private class MyController : RequestController
|
||||
{
|
||||
//controller method.
|
||||
// ReSharper disable UnusedMemberInPrivateClass
|
||||
public string Hello()
|
||||
// ReSharper restore UnusedMemberInPrivateClass
|
||||
{
|
||||
Assert.False(string.IsNullOrEmpty(Id));
|
||||
if (Request.Method == Method.Post)
|
||||
return Request.Form["user"]["firstName"].Value;
|
||||
|
||||
return Request.QueryString["hello"].Value + "|" + Id;
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return new MyController();
|
||||
}
|
||||
}
|
||||
|
||||
private class MyContext : IHttpClientContext
|
||||
{
|
||||
private const bool _secured = false;
|
||||
|
||||
/// <summary>
|
||||
/// Using SSL or other encryption method.
|
||||
/// </summary>
|
||||
public bool Secured
|
||||
{
|
||||
get { return _secured; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Using SSL or other encryption method.
|
||||
/// </summary>
|
||||
public bool IsSecured
|
||||
{
|
||||
get { return _secured; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from client
|
||||
/// </summary>
|
||||
/// <param name="error">error to report in the <see cref="Disconnected"/> event.</param>
|
||||
public void Disconnect(SocketError error)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a response.
|
||||
/// </summary>
|
||||
/// <param name="httpVersion">Either HttpHelper.HTTP10 or HttpHelper.HTTP11</param>
|
||||
/// <param name="statusCode">http status code</param>
|
||||
/// <param name="reason">reason for the status code.</param>
|
||||
/// <param name="body">html body contents, can be null or empty.</param>
|
||||
/// <param name="contentType">A content type to return the body as, ie 'text/html' or 'text/plain', defaults to 'text/html' if null or empty</param>
|
||||
/// <exception cref="ArgumentException">If httpVersion is invalid.</exception>
|
||||
public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a response.
|
||||
/// </summary>
|
||||
/// <param name="httpVersion">Either HttpHelper.HTTP10 or HttpHelper.HTTP11</param>
|
||||
/// <param name="statusCode">http status code</param>
|
||||
/// <param name="reason">reason for the status code.</param>
|
||||
public void Respond(string httpVersion, HttpStatusCode statusCode, string reason)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a response.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public void Respond(string body)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// send a whole buffer
|
||||
/// </summary>
|
||||
/// <param name="buffer">buffer to send</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public void Send(byte[] buffer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data using the stream
|
||||
/// </summary>
|
||||
/// <param name="buffer">Contains data to send</param>
|
||||
/// <param name="offset">Start position in buffer</param>
|
||||
/// <param name="size">number of bytes to send</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public void Send(byte[] buffer, int offset, int size)
|
||||
{
|
||||
}
|
||||
|
||||
public void ShutupWarnings()
|
||||
{
|
||||
Disconnected(this, null);
|
||||
RequestReceived(this, null);
|
||||
}
|
||||
|
||||
public event EventHandler<DisconnectedEventArgs> Disconnected;
|
||||
public event EventHandler<RequestEventArgs> RequestReceived;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// This attribute tells the controller that the method
|
||||
/// uses SendHeader and/or SendBody to handle the request.
|
||||
/// </summary>
|
||||
public class RawHandlerAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,587 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Web;
|
||||
using HttpServer.Authentication;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.HttpModules;
|
||||
using HttpServer.Sessions;
|
||||
|
||||
namespace HttpServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller in the Model-View-Controller pattern.
|
||||
/// Derive this class and add method with one of the following signatures:
|
||||
/// "public string MethodName()" or "public void MyMethod()".
|
||||
///
|
||||
/// The first should return a string with the response, the latter
|
||||
/// should use SendHeader and SendBody methods to handle the response.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Last segment of the path is always broken into the properties Id and RequestedType
|
||||
/// Alhtough note that the RequestedType can also be empty if no file extension have
|
||||
/// been specified. A typical use of file extensions in controllers is to specify which type of
|
||||
/// format to return.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// public class MyController : RequestController
|
||||
/// {
|
||||
/// public string Hello()
|
||||
/// {
|
||||
/// if (RequestedType == "xml")
|
||||
/// return "<hello>World<hello>";
|
||||
/// else
|
||||
/// return "Hello " + Request.QueryString["user"].Value + ", welcome to my world";
|
||||
/// }
|
||||
///
|
||||
/// public void File()
|
||||
/// {
|
||||
/// Response.Headers.ContentType = "text/xml";
|
||||
/// Response.SendHeader();
|
||||
/// }
|
||||
/// }
|
||||
/// </example>
|
||||
/// <seealso cref="ControllerNameAttribute"/>
|
||||
/// <seealso cref="AuthRequiredAttribute"/>
|
||||
/// <seealso cref="AuthValidatorAttribute"/>
|
||||
public abstract class RequestController : HttpModule, ICloneable
|
||||
{
|
||||
private const string Html = "html";
|
||||
private readonly LinkedList<MethodInfo> _beforeFilters = new LinkedList<MethodInfo>();
|
||||
private readonly Dictionary<string, MethodInfo> _binaryMethods = new Dictionary<string, MethodInfo>();
|
||||
private readonly Dictionary<string, int> _authMethods = new Dictionary<string, int>();
|
||||
private readonly Dictionary<string, MethodInfo> _methods = new Dictionary<string, MethodInfo>();
|
||||
private LinkedListNode<MethodInfo> _lastMiddleFilter;
|
||||
private string _controllerName;
|
||||
private MethodInfo _defaultMethod;
|
||||
private string _defaultMethodStr;
|
||||
private MethodInfo _authValidator;
|
||||
|
||||
//used temp during method mapping.
|
||||
private string _id;
|
||||
private MethodInfo _method;
|
||||
private string _methodName;
|
||||
private IHttpRequest _request;
|
||||
private string _requestedExtension;
|
||||
private IHttpResponse _response;
|
||||
private IHttpSession _session;
|
||||
|
||||
/// <summary>
|
||||
/// create a new request controller
|
||||
/// </summary>
|
||||
/// <param name="controller">prototype to copy information from</param>
|
||||
protected RequestController(RequestController controller)
|
||||
{
|
||||
_beforeFilters = controller._beforeFilters;
|
||||
_binaryMethods = controller._binaryMethods;
|
||||
_authMethods = controller._authMethods;
|
||||
_methods = controller._methods;
|
||||
_controllerName = controller.ControllerName;
|
||||
_defaultMethod = controller._defaultMethod;
|
||||
_defaultMethodStr = controller._defaultMethodStr;
|
||||
_authValidator = controller._authValidator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// create a new controller
|
||||
/// </summary>
|
||||
protected RequestController()
|
||||
{
|
||||
MapMethods();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// object that was attached during http authentication process.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You can also assign this tag yourself if you are using regular
|
||||
/// http page login.
|
||||
/// </remarks>
|
||||
/// <seealso cref="AuthModule"/>
|
||||
protected object AuthenticationTag
|
||||
{
|
||||
get { return _session[AuthModule.AuthenticationTag]; }
|
||||
set { _session[AuthModule.AuthenticationTag] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of this controller (class name without the "Controller" part)
|
||||
/// </summary>
|
||||
public string ControllerName
|
||||
{
|
||||
get { return _controllerName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the method to use if no action have been specified.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">If specified method do not exist.</exception>
|
||||
public string DefaultMethod
|
||||
{
|
||||
get { return _defaultMethodStr; }
|
||||
set
|
||||
{
|
||||
if (_methods.ContainsKey(value.ToLower()))
|
||||
{
|
||||
_defaultMethodStr = value.ToLower();
|
||||
_defaultMethod = _methods[_defaultMethodStr];
|
||||
}
|
||||
else if (_binaryMethods.ContainsKey(value.ToLower()))
|
||||
{
|
||||
_defaultMethodStr = value.ToLower();
|
||||
_defaultMethod = _binaryMethods[_defaultMethodStr];
|
||||
}
|
||||
else
|
||||
throw new ArgumentException("New DefaultMethod value is not a valid controller method.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Id is the third part of the uri path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Is extracted as in: /controllername/methodname/id/
|
||||
/// </para>
|
||||
/// <para>string.Empty if not specified.</para>
|
||||
/// </remarks>
|
||||
/// <example></example>
|
||||
public string Id
|
||||
{
|
||||
get { return _id ?? string.Empty; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method currently being invoked.
|
||||
/// Always in lower case.
|
||||
/// </summary>
|
||||
public string MethodName
|
||||
{
|
||||
get { return _methodName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request information (like Url, form, querystring etc)
|
||||
/// </summary>
|
||||
protected IHttpRequest Request
|
||||
{
|
||||
get { return _request; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension if a filename was specified.
|
||||
/// </summary>
|
||||
public string RequestedExtension
|
||||
{
|
||||
get { return _requestedExtension; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response information (that is going to be sent back to the browser/client)
|
||||
/// </summary>
|
||||
protected IHttpResponse Response
|
||||
{
|
||||
get { return _response; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Session information, is stored between requests as long as the session cookie is valid.
|
||||
/// </summary>
|
||||
protected IHttpSession Session
|
||||
{
|
||||
get { return _session; }
|
||||
}
|
||||
|
||||
/*
|
||||
protected virtual void AddAuthAttribute(string methodName, object attribute)
|
||||
{
|
||||
if (attribute.GetType() == typeof (AuthenticatorAttribute))
|
||||
{
|
||||
AuthenticatorAttribute attrib = (AuthenticatorAttribute) attribute;
|
||||
try
|
||||
{
|
||||
MethodInfo mi = GetType().GetMethod(attrib.Method);
|
||||
if (methodName == ClassMethodName)
|
||||
_classCheckAuthMethod = mi;
|
||||
else
|
||||
_authMethods.Add(methodName, mi);
|
||||
}
|
||||
catch (AmbiguousMatchException err)
|
||||
{
|
||||
if (methodName == "class")
|
||||
throw new InvalidOperationException(
|
||||
"Failed to find Authenticator method for class " + GetType().Name, err);
|
||||
else
|
||||
throw new InvalidOperationException("Failed to find Authenticator method for " + GetType().Name +
|
||||
"." + methodName);
|
||||
}
|
||||
}
|
||||
else
|
||||
throw new ArgumentException("Attribute is not of type AuthenticatorAttribute");
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Method that determines if an url should be handled or not by the module
|
||||
/// </summary>
|
||||
/// <param name="request">Url requested by the client.</param>
|
||||
/// <returns>true if module should handle the url.</returns>
|
||||
public virtual bool CanHandle(IHttpRequest request)
|
||||
{
|
||||
if (request.UriParts.Length <= 0)
|
||||
return false;
|
||||
|
||||
// check if controller name is correct. uri segments adds a slash to the segments
|
||||
if (string.Compare(request.UriParts[0], _controllerName, true) != 0)
|
||||
return false;
|
||||
|
||||
// check action
|
||||
if (request.UriParts.Length > 1)
|
||||
{
|
||||
string uriPart = request.UriParts[1];
|
||||
int pos = uriPart.LastIndexOf('.');
|
||||
if (pos != -1)
|
||||
uriPart = uriPart.Substring(0, pos);
|
||||
if (_methods.ContainsKey(uriPart) || _binaryMethods.ContainsKey(uriPart))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (request.UriParts.Length == 1)
|
||||
return _defaultMethod != null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines which method to use.
|
||||
/// </summary>
|
||||
/// <param name="request">Requested resource</param>
|
||||
protected virtual MethodInfo GetMethod(IHttpRequest request)
|
||||
{
|
||||
// Check where the default met
|
||||
if (request.UriParts.Length <= 1)
|
||||
return _defaultMethod;
|
||||
|
||||
string uriPart = request.UriParts[1];
|
||||
int pos = uriPart.LastIndexOf('.');
|
||||
if (pos != -1)
|
||||
{
|
||||
_requestedExtension = uriPart.Substring(pos + 1);
|
||||
uriPart = uriPart.Substring(0, pos);
|
||||
}
|
||||
|
||||
if (_methods.ContainsKey(uriPart))
|
||||
return _methods[uriPart];
|
||||
|
||||
if (_binaryMethods.ContainsKey(uriPart))
|
||||
return _binaryMethods[uriPart];
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call all before filters
|
||||
/// </summary>
|
||||
/// <returns>true if a before filter wants to abort the processing.</returns>
|
||||
private bool InvokeBeforeFilters()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (MethodInfo info in _beforeFilters)
|
||||
if (!(bool) info.Invoke(this, null))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (TargetInvocationException err)
|
||||
{
|
||||
#if DEBUG
|
||||
FieldInfo remoteStackTraceString =
|
||||
typeof(Exception).GetField("_remoteStackTraceString",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
remoteStackTraceString.SetValue(err.InnerException, err.InnerException.StackTrace + Environment.NewLine);
|
||||
throw err.InnerException;
|
||||
#else
|
||||
throw new InternalServerException("Controller filter failure, please try again.", err);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to be able to process result
|
||||
/// returned by controller method.
|
||||
/// </summary>
|
||||
protected virtual void InvokeMethod()
|
||||
{
|
||||
try
|
||||
{
|
||||
InvokeMethodInternal();
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
OnUnhandledException(err);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method if you want to be able to
|
||||
/// handle unhanded exceptions
|
||||
/// </summary>
|
||||
/// <param name="err">thrown exception</param>
|
||||
/// <remarks>Don't "eat" exceptions derived from HttpException since
|
||||
/// they are handled by the framework,unless your are sure of what you are
|
||||
/// doing..</remarks>
|
||||
protected virtual void OnUnhandledException(Exception err)
|
||||
{
|
||||
throw err;
|
||||
}
|
||||
|
||||
private void InvokeMethodInternal()
|
||||
{
|
||||
if (_authMethods.ContainsKey(_methodName))
|
||||
{
|
||||
if (_authValidator != null)
|
||||
{
|
||||
if (_authValidator.GetParameters().Length == 1)
|
||||
{
|
||||
if (!(bool) _authValidator.Invoke(this, new object[] {_authMethods[_methodName]}))
|
||||
return;
|
||||
}
|
||||
// backwards compatible.
|
||||
else
|
||||
if (!(bool)_authValidator.Invoke(this, null))
|
||||
throw new UnauthorizedException("Need to authenticate.");
|
||||
}
|
||||
}
|
||||
|
||||
if (_method.ReturnType == typeof (string))
|
||||
{
|
||||
try
|
||||
{
|
||||
string temp = (string) _method.Invoke(this, null);
|
||||
if (temp != null)
|
||||
{
|
||||
TextWriter writer = new StreamWriter(Response.Body);
|
||||
writer.Write(temp);
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
catch (TargetInvocationException err)
|
||||
{
|
||||
#if DEBUG
|
||||
FieldInfo remoteStackTraceString =
|
||||
typeof (Exception).GetField("_remoteStackTraceString",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
remoteStackTraceString.SetValue(err.InnerException, err.InnerException.StackTrace + Environment.NewLine);
|
||||
throw err.InnerException;
|
||||
#else
|
||||
throw new InternalServerException("Controller failure, please try again.", err);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_method.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
/*
|
||||
/// <summary>
|
||||
/// check authentication attributes for the class
|
||||
/// </summary>
|
||||
protected virtual void MapClassAuth()
|
||||
{
|
||||
object[] attributes = GetType().GetCustomAttributes(true);
|
||||
foreach (object attribute in attributes)
|
||||
{
|
||||
if (attribute.GetType() == typeof (AuthenticatorAttribute))
|
||||
AddAuthAttribute(ClassMethodName, attribute);
|
||||
if (attribute.GetType() == typeof (AuthenticationRequiredAttribute))
|
||||
AddCheckAuthAttribute(ClassMethodName, attribute);
|
||||
}
|
||||
}
|
||||
*/
|
||||
/// <summary>
|
||||
/// This method goes through all methods in the controller and
|
||||
/// add's them to a dictionary. They are later used to invoke
|
||||
/// the correct method depending on the url
|
||||
/// </summary>
|
||||
private void MapMethods()
|
||||
{
|
||||
lock (_methods)
|
||||
{
|
||||
// already mapped.
|
||||
if (_methods.Count > 0)
|
||||
return;
|
||||
|
||||
object[] controllerNameAttrs = GetType().GetCustomAttributes(typeof (ControllerNameAttribute), false);
|
||||
if (controllerNameAttrs.Length > 0)
|
||||
_controllerName = ((ControllerNameAttribute)controllerNameAttrs[0]).Name;
|
||||
else
|
||||
{
|
||||
_controllerName = GetType().Name;
|
||||
if (ControllerName.Contains("Controller"))
|
||||
_controllerName = ControllerName.Replace("Controller", "");
|
||||
_controllerName = ControllerName.ToLower();
|
||||
}
|
||||
|
||||
MethodInfo[] methods =
|
||||
GetType().GetMethods(BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance);
|
||||
foreach (MethodInfo info in methods)
|
||||
{
|
||||
ParameterInfo[] parameters = info.GetParameters();
|
||||
|
||||
// find regular render methods
|
||||
if (parameters.Length == 0 && info.ReturnType == typeof (string))
|
||||
{
|
||||
string name = info.Name.ToLower();
|
||||
if (name.Length > 3 && (name.Substring(0, 4) == "get_" || name.Substring(0, 4) == "set_"))
|
||||
continue;
|
||||
if (name == "tostring")
|
||||
continue;
|
||||
|
||||
// Add authenticators
|
||||
object[] authAttributes = info.GetCustomAttributes(true);
|
||||
foreach (object attribute in authAttributes)
|
||||
if (attribute.GetType() == typeof (AuthRequiredAttribute))
|
||||
_authMethods.Add(info.Name.ToLower(), ((AuthRequiredAttribute)attribute).Level);
|
||||
_methods.Add(info.Name.ToLower(), info);
|
||||
}
|
||||
|
||||
// find raw handlers
|
||||
object[] attributes = info.GetCustomAttributes(typeof (RawHandlerAttribute), true);
|
||||
if (attributes.Length >= 1 && info.ReturnType == typeof (void) && parameters.Length == 0)
|
||||
{
|
||||
// Add authenticators
|
||||
object[] authAttributes = info.GetCustomAttributes(true);
|
||||
foreach (object attribute in authAttributes)
|
||||
if (attribute.GetType() == typeof(AuthRequiredAttribute))
|
||||
_authMethods.Add(info.Name.ToLower(), ((AuthRequiredAttribute)attribute).Level);
|
||||
_binaryMethods.Add(info.Name.ToLower(), info);
|
||||
}
|
||||
} //foreach
|
||||
|
||||
methods = GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
foreach (MethodInfo info in methods)
|
||||
{
|
||||
ParameterInfo[] parameters = info.GetParameters();
|
||||
|
||||
// find before filters.
|
||||
if (parameters.Length == 0 && info.ReturnType == typeof (bool))
|
||||
{
|
||||
object[] authAttributes = info.GetCustomAttributes(true);
|
||||
foreach (object attribute in authAttributes)
|
||||
if (attribute.GetType() == typeof(AuthValidatorAttribute))
|
||||
{
|
||||
if (_authValidator != null)
|
||||
throw new InvalidOperationException("Auth validator have already been specified.");
|
||||
_authValidator = info;
|
||||
}
|
||||
else if (attribute.GetType() == typeof (BeforeFilterAttribute))
|
||||
{
|
||||
BeforeFilterAttribute attr = (BeforeFilterAttribute) attribute;
|
||||
LinkedListNode<MethodInfo> node = new LinkedListNode<MethodInfo>(info);
|
||||
|
||||
|
||||
if (attr.Position == FilterPosition.First)
|
||||
_beforeFilters.AddFirst(node);
|
||||
else if (attr.Position == FilterPosition.Last)
|
||||
_beforeFilters.AddLast(node);
|
||||
else
|
||||
{
|
||||
if (_lastMiddleFilter == null)
|
||||
_beforeFilters.AddLast(node);
|
||||
else
|
||||
_beforeFilters.AddAfter(_lastMiddleFilter, node);
|
||||
|
||||
_lastMiddleFilter = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Map index method.
|
||||
MethodInfo mi = GetType().GetMethod("Index", BindingFlags.Public | BindingFlags.Instance);
|
||||
if (mi != null && mi.ReturnType == typeof(string) && mi.GetParameters().Length == 0)
|
||||
DefaultMethod = "Index";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that process the url
|
||||
/// </summary>
|
||||
/// <param name="request">Uses Uri and QueryString to determine method.</param>
|
||||
/// <param name="response">Relays response object to invoked method.</param>
|
||||
/// <param name="session">Relays session object to invoked method. </param>
|
||||
public override bool Process(IHttpRequest request, IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
if (!CanHandle(request))
|
||||
return false;
|
||||
|
||||
SetupRequest(request, response, session);
|
||||
|
||||
if (InvokeBeforeFilters())
|
||||
return true;
|
||||
|
||||
InvokeMethod();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will assign all variables that are unique for each session
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="session"></param>
|
||||
protected virtual void SetupRequest(IHttpRequest request, IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
_requestedExtension = Html;
|
||||
|
||||
// extract id
|
||||
if (request.Uri.Segments.Length > 3)
|
||||
{
|
||||
_id = request.Uri.Segments[3];
|
||||
if (_id.EndsWith("/"))
|
||||
_id = _id.Substring(0, _id.Length - 1);
|
||||
else
|
||||
{
|
||||
int pos = _id.LastIndexOf('.');
|
||||
if (pos != -1)
|
||||
{
|
||||
_requestedExtension = _id.Substring(pos + 1);
|
||||
_id = _id.Substring(0, pos);
|
||||
}
|
||||
}
|
||||
_id = HttpUtility.UrlDecode(_id);
|
||||
}
|
||||
else if (request.QueryString["id"] != HttpInputItem.Empty)
|
||||
_id = HttpUtility.UrlDecode(request.QueryString["id"].Value);
|
||||
else
|
||||
_id = string.Empty;
|
||||
|
||||
_request = request;
|
||||
_response = response;
|
||||
_session = session;
|
||||
|
||||
if (request.Uri.Segments.Length == 2 && _defaultMethod == null)
|
||||
throw new NotFoundException("No default method is specified.");
|
||||
|
||||
_method = GetMethod(request);
|
||||
if (_method == null)
|
||||
throw new NotFoundException("Requested action could not be found.");
|
||||
|
||||
_methodName = _method.Name.ToLower();
|
||||
}
|
||||
|
||||
#region ICloneable Members
|
||||
|
||||
/// <summary>
|
||||
/// Make a clone of this controller
|
||||
/// </summary>
|
||||
/// <returns>a new controller with the same base information as this one.</returns>
|
||||
public abstract object Clone();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml.Serialization;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.Helpers;
|
||||
using HttpServer.Rendering;
|
||||
using HttpServer.Sessions;
|
||||
|
||||
namespace HttpServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// View controllers integrates the templates, by adding
|
||||
/// Render methods.
|
||||
/// </summary>
|
||||
public abstract class ViewController : RequestController
|
||||
{
|
||||
private readonly TemplateArguments _arguments = new TemplateArguments();
|
||||
private readonly TemplateManager _templateMgr;
|
||||
private NameValueCollection _errors = new NameValueCollection();
|
||||
private bool _includeLayoutInAjaxRequests;
|
||||
private string _layout = "Application";
|
||||
private string _title;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ViewController"/>.
|
||||
/// </summary>
|
||||
protected ViewController(TemplateManager mgr)
|
||||
{
|
||||
_templateMgr = mgr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ViewController"/>.
|
||||
/// </summary>
|
||||
/// <param name="controller">prototype to copy information from.</param>
|
||||
protected ViewController(ViewController controller)
|
||||
: base(controller)
|
||||
{
|
||||
_templateMgr = controller._templateMgr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arguments that are being used in the templates.
|
||||
/// </summary>
|
||||
protected TemplateArguments Arguments
|
||||
{
|
||||
get { return _arguments; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A set of errors that occurred during request processing.
|
||||
/// Key should be argument name (if argument error, otherwise <see cref="String.Empty"/>), value should be
|
||||
/// the error message.
|
||||
/// </summary>
|
||||
/// <remarks>Errors can be rendered into templates using the <see cref="WebHelper.Errors"/> method.</remarks>
|
||||
/// <seealso cref="WebHelper"/>
|
||||
[XmlElement("Errors")]
|
||||
protected NameValueCollection Errors
|
||||
{
|
||||
get { return _errors; }
|
||||
set { _errors = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if we always should render contents inside page layouts when request is Ajax.
|
||||
/// </summary>
|
||||
/// <remarks>default is false.</remarks>
|
||||
public bool IncludeLayoutInAjaxRequests
|
||||
{
|
||||
get { return _includeLayoutInAjaxRequests; }
|
||||
set { _includeLayoutInAjaxRequests = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Which page layout to use (without file extension)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Page layouts should be places in the Views\Layouts folder.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// default is "Application"
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public string Layout
|
||||
{
|
||||
get { return _layout; }
|
||||
set { _layout = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Page title (are added as a parameter to the layout template, use it in <title> HTML tag.
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get { return _title; }
|
||||
set { _title = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render template for the currently invoked method.
|
||||
/// </summary>
|
||||
/// <param name="args">arguments/parameters used in template</param>
|
||||
/// <returns>template generated content</returns>
|
||||
/// <remarks>calls RenderActionWithErrors</remarks>
|
||||
protected string Render(params object[] args)
|
||||
{
|
||||
return RenderAction(MethodName, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render contents into a template.
|
||||
/// </summary>
|
||||
/// <param name="method">method/template to generate</param>
|
||||
/// <param name="args">arguments/parameters used in template</param>
|
||||
/// <returns>template generated content</returns>
|
||||
/// <remarks>calls RenderActionWithErrors.</remarks>
|
||||
protected virtual string RenderAction(string method, params object[] args)
|
||||
{
|
||||
if (!Arguments.Contains("Errors"))
|
||||
Arguments.Add("Errors", _errors);
|
||||
string pageTemplate = RenderTemplate(ControllerName, method, args);
|
||||
|
||||
// 1. dont render main layout for ajax requests, since they just update partial
|
||||
// parts of the web page.
|
||||
// 2. Dont render html layout for other stuff than html
|
||||
if (Request.IsAjax && !_includeLayoutInAjaxRequests)
|
||||
return pageTemplate;
|
||||
|
||||
return RenderLayout(_layout, pageTemplate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge arguments array and Arguments property.
|
||||
/// </summary>
|
||||
/// <param name="args">Arguments array to merge</param>
|
||||
/// <returns>arguments/parameters that can be used in the template.</returns>
|
||||
/// <remarks>Will add Request/Response/Session arguments</remarks>
|
||||
private TemplateArguments MergeArguments(object[] args)
|
||||
{
|
||||
// Create a new argument holder
|
||||
TemplateArguments arguments = new TemplateArguments();
|
||||
arguments.Add("Request", Request, typeof(IHttpRequest));
|
||||
arguments.Add("Response", Response);
|
||||
arguments.Add("Session", Session);
|
||||
arguments.Add("Controller", this, typeof(ViewController));
|
||||
arguments.Update(_arguments);
|
||||
arguments.Update(new TemplateArguments(args));
|
||||
|
||||
return arguments;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Renders errors from the <see cref="Errors"/> property into the
|
||||
/// current method template, or as a JavaScript alert if the request is Ajax.
|
||||
/// </summary>
|
||||
/// <param name="method">name of the currently invoked method.</param>
|
||||
/// <param name="arguments">arguments used in the method template.</param>
|
||||
/// <returns>generated string</returns>
|
||||
/// <remarks>Creates a JavaScript Alert box if request is Ajax.</remarks>
|
||||
protected string RenderErrors(string method, params object[] arguments)
|
||||
{
|
||||
if (_errors.Count > 0)
|
||||
{
|
||||
if (Request.IsAjax)
|
||||
return RenderJsErrors(_errors);
|
||||
}
|
||||
|
||||
return RenderAction(method, arguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders errors from the <see cref="Errors"/> property into the
|
||||
/// current method template, or as a JavaScript alert if the request is Ajax.
|
||||
/// </summary>
|
||||
/// <param name="errors">A collection of errors.</param>
|
||||
/// <param name="method">name of the currently invoked method.</param>
|
||||
/// <param name="arguments">arguments used in the method template.</param>
|
||||
/// <returns>generated string</returns>
|
||||
/// <remarks>Creates a JavaScript Alert box if request is Ajax.</remarks>
|
||||
protected string RenderErrors(NameValueCollection errors, string method, params object[] arguments)
|
||||
{
|
||||
if (_errors.Count > 0)
|
||||
{
|
||||
if (Request.IsAjax)
|
||||
return RenderJsErrors(_errors);
|
||||
}
|
||||
|
||||
Arguments.Add("Errors", errors);
|
||||
return RenderAction(method, arguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switches content-type to "text/JavaScript" and returns content.
|
||||
/// </summary>
|
||||
/// <param name="js">JavaScript to send to the client.</param>
|
||||
/// <returns>JavaScript</returns>
|
||||
protected string RenderJavascript(string js)
|
||||
{
|
||||
Response.ContentType = "text/javascript";
|
||||
return js;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a JavaScript "alert" filled with all errors.
|
||||
/// </summary>
|
||||
/// <param name="errors"></param>
|
||||
/// <returns>a</returns>
|
||||
protected string RenderJsErrors(NameValueCollection errors)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("alert('");
|
||||
for (int i = 0; i < errors.Count; ++i)
|
||||
sb.Append(errors[i].Replace("'", "\\'") + "\\n");
|
||||
sb.Append("');");
|
||||
|
||||
Response.ContentType = "text/javascript";
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// renders one of the layouts
|
||||
/// </summary>
|
||||
/// <param name="layoutName">layout to render (should be found in the "views\\layouts" folder).</param>
|
||||
/// <param name="contents">contents will be put in the template variable called "text".</param>
|
||||
/// <returns>generated text/HTML.</returns>
|
||||
protected virtual string RenderLayout(string layoutName, string contents)
|
||||
{
|
||||
// template engine will find all layouts and take the first one with that one.
|
||||
return RenderTemplate("layouts", layoutName, "text", contents,
|
||||
"title", Title ?? "The page with no name");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render a template.
|
||||
/// </summary>
|
||||
/// <remarks>Merges the Arguments property with the <c>args</c> parameter and pass those to the template.</remarks>
|
||||
/// <param name="controller">controller name are used as a folder name when looking for the template.</param>
|
||||
/// <param name="method">method are used as filename when looking for the template.</param>
|
||||
/// <param name="args">arguments that should be passed to the template.</param>
|
||||
/// <returns></returns>
|
||||
protected string RenderTemplate(string controller, string method, params object[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
TemplateArguments args2 = MergeArguments(args);
|
||||
_arguments.Clear();
|
||||
|
||||
string result = _templateMgr.Render(controller + "\\" + method + ".*", args2);
|
||||
_errors.Clear();
|
||||
return result;
|
||||
}
|
||||
catch (FileNotFoundException err)
|
||||
{
|
||||
throw new NotFoundException("Failed to find template. Details: " + err.Message, err);
|
||||
}
|
||||
catch (InvalidOperationException err)
|
||||
{
|
||||
throw new InternalServerException("Failed to render template. Details: " + err.Message, err);
|
||||
}
|
||||
catch (Fadd.CompilerException err)
|
||||
{
|
||||
throw new TemplateException("Could not compile template '" + controller + "/" + method + "/'", err);
|
||||
}
|
||||
catch (CodeGeneratorException err)
|
||||
{
|
||||
#if DEBUG
|
||||
string error = "Line: " + err.LineNumber + "<br />\r\n" + err.ToString().Replace("\r\n", "<br />\r\n");
|
||||
throw new InternalServerException(error, err);
|
||||
#else
|
||||
throw new InternalServerException("Failed to compile template.", err);
|
||||
#endif
|
||||
}
|
||||
catch (ArgumentException err)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new InternalServerException(
|
||||
"Failed to render template, reason: " + err.ToString().Replace("\r\n", "<br />\r\n"), err);
|
||||
#else
|
||||
throw new InternalServerException("Failed to render templates", err);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked each time a new request is about to be invoked.
|
||||
/// </summary>
|
||||
/// <remarks>Can be used to clear old data.</remarks>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="session"></param>
|
||||
protected override void SetupRequest(IHttpRequest request, IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
_arguments.Clear();
|
||||
_errors.Clear();
|
||||
base.SetupRequest(request, response, session);
|
||||
_layout = Request.Param["layout"].Value ?? Layout;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// An unhandled exception have been caught by the system.
|
||||
/// </summary>
|
||||
public class ExceptionEventArgs : EventArgs
|
||||
{
|
||||
private readonly Exception _exception;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExceptionEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="exception">Caught exception.</param>
|
||||
public ExceptionEventArgs(Exception exception)
|
||||
{
|
||||
_exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// caught exception
|
||||
/// </summary>
|
||||
public Exception Exception
|
||||
{
|
||||
get { return _exception; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// We dont want to let the server to die due to exceptions thrown in worker threads.
|
||||
/// therefore we use this delegate to give you a change to handle uncaught exceptions.
|
||||
/// </summary>
|
||||
/// <param name="source">Class that the exception was thrown in.</param>
|
||||
/// <param name="exception">Exception</param>
|
||||
/// <remarks>
|
||||
/// Server will throw a InternalServerException in release version if you dont
|
||||
/// handle this delegate.
|
||||
/// </remarks>
|
||||
public delegate void ExceptionHandler(object source, Exception exception);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace HttpServer.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The request could not be understood by the server due to malformed syntax.
|
||||
/// The client SHOULD NOT repeat the request without modifications.
|
||||
///
|
||||
/// Text taken from: http://www.submissionchamber.com/help-guides/error-codes.php
|
||||
/// </summary>
|
||||
public class BadRequestException : HttpException
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new bad request exception.
|
||||
/// </summary>
|
||||
/// <param name="errMsg">reason to why the request was bad.</param>
|
||||
public BadRequestException(string errMsg)
|
||||
: base(HttpStatusCode.BadRequest, errMsg)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new bad request exception.
|
||||
/// </summary>
|
||||
/// <param name="errMsg">reason to why the request was bad.</param>
|
||||
/// <param name="inner">inner exception</param>
|
||||
public BadRequestException(string errMsg, Exception inner)
|
||||
: base(HttpStatusCode.BadRequest, errMsg, inner)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System.Net;
|
||||
|
||||
namespace HttpServer.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The server understood the request, but is refusing to fulfill it.
|
||||
/// Authorization will not help and the request SHOULD NOT be repeated.
|
||||
/// If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled,
|
||||
/// it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information
|
||||
/// available to the client, the status code 404 (Not Found) can be used instead.
|
||||
///
|
||||
/// Text taken from: http://www.submissionchamber.com/help-guides/error-codes.php
|
||||
/// </summary>
|
||||
public class ForbiddenException : HttpException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ForbiddenException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="errorMsg">error message</param>
|
||||
public ForbiddenException(string errorMsg)
|
||||
: base(HttpStatusCode.Forbidden, errorMsg)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace HttpServer.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// All HTTP based exceptions will derive this class.
|
||||
/// </summary>
|
||||
public class HttpException : Exception
|
||||
{
|
||||
private readonly HttpStatusCode _code;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new HttpException
|
||||
/// </summary>
|
||||
/// <param name="code">http status code (sent in the response)</param>
|
||||
/// <param name="message">error description</param>
|
||||
public HttpException(HttpStatusCode code, string message) : base(code + ": " + message)
|
||||
{
|
||||
_code = code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new HttpException
|
||||
/// </summary>
|
||||
/// <param name="code">http status code (sent in the response)</param>
|
||||
/// <param name="message">error description</param>
|
||||
/// <param name="inner">inner exception</param>
|
||||
public HttpException(HttpStatusCode code, string message, Exception inner)
|
||||
: base(code + ": " + message, inner)
|
||||
{
|
||||
_code = code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// status code to use in the response.
|
||||
/// </summary>
|
||||
public HttpStatusCode HttpStatusCode
|
||||
{
|
||||
get { return _code; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace HttpServer.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The server encountered an unexpected condition which prevented it from fulfilling the request.
|
||||
/// </summary>
|
||||
public class InternalServerException : HttpException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InternalServerException"/> class.
|
||||
/// </summary>
|
||||
public InternalServerException()
|
||||
: base(HttpStatusCode.InternalServerError, "The server encountered an unexpected condition which prevented it from fulfilling the request.")
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InternalServerException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">error message.</param>
|
||||
public InternalServerException(string message)
|
||||
: base(HttpStatusCode.InternalServerError, message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InternalServerException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">error message.</param>
|
||||
/// <param name="inner">inner exception.</param>
|
||||
public InternalServerException(string message, Exception inner)
|
||||
: base(HttpStatusCode.InternalServerError, message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace HttpServer.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The requested resource was not found in the web server.
|
||||
/// </summary>
|
||||
public class NotFoundException : HttpException
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new exception
|
||||
/// </summary>
|
||||
/// <param name="message">message describing the error</param>
|
||||
/// <param name="inner">inner exception</param>
|
||||
public NotFoundException(string message, Exception inner) : base(HttpStatusCode.NotFound, message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new exception
|
||||
/// </summary>
|
||||
/// <param name="message">message describing the error</param>
|
||||
public NotFoundException(string message)
|
||||
: base(HttpStatusCode.NotFound, message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace HttpServer.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The request requires user authentication. The response MUST include a
|
||||
/// WWW-Authenticate header field (section 14.47) containing a challenge
|
||||
/// applicable to the requested resource.
|
||||
///
|
||||
/// The client MAY repeat the request with a suitable Authorization header
|
||||
/// field (section 14.8). If the request already included Authorization
|
||||
/// credentials, then the 401 response indicates that authorization has been
|
||||
/// refused for those credentials. If the 401 response contains the same challenge
|
||||
/// as the prior response, and the user agent has already attempted authentication
|
||||
/// at least once, then the user SHOULD be presented the entity that was given in the response,
|
||||
/// since that entity might include relevant diagnostic information.
|
||||
///
|
||||
/// HTTP access authentication is explained in rfc2617:
|
||||
/// http://www.ietf.org/rfc/rfc2617.txt
|
||||
///
|
||||
/// (description is taken from
|
||||
/// http://www.submissionchamber.com/help-guides/error-codes.php#sec10.4.2)
|
||||
/// </summary>
|
||||
public class UnauthorizedException : HttpException
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new unauhtorized exception.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnauthorizedException"/>
|
||||
public UnauthorizedException()
|
||||
: base(HttpStatusCode.Unauthorized, "The request requires user authentication.")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new unauhtorized exception.
|
||||
/// </summary>
|
||||
/// <param name="message">reason to why the request was unauthorized.</param>
|
||||
/// <param name="inner">inner exception</param>
|
||||
public UnauthorizedException(string message, Exception inner)
|
||||
: base(HttpStatusCode.Unauthorized, message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new unauhtorized exception.
|
||||
/// </summary>
|
||||
/// <param name="message">reason to why the request was unauthorized.</param>
|
||||
public UnauthorizedException(string message)
|
||||
: base(HttpStatusCode.Unauthorized, message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using HttpServer;
|
||||
|
||||
namespace HttpServer.FormDecoders
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for form content decoders.
|
||||
/// </summary>
|
||||
public interface FormDecoder
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream containing the content</param>
|
||||
/// <param name="contentType">Content type (with any additional info like boundry). Content type is always supplied in lower case</param>
|
||||
/// <param name="encoding">Stream enconding</param>
|
||||
/// <returns>A http form, or null if content could not be parsed.</returns>
|
||||
/// <exception cref="InvalidDataException">If contents in the stream is not valid input data.</exception>
|
||||
HttpForm Decode(Stream stream, string contentType, Encoding encoding);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the decoder can handle the mime type
|
||||
/// </summary>
|
||||
/// <param name="contentType">Content type (with any additional info like boundry). Content type is always supplied in lower case.</param>
|
||||
/// <returns>True if the decoder can parse the specified content type</returns>
|
||||
bool CanParse(string contentType);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer.FormDecoders
|
||||
{
|
||||
/// <summary>
|
||||
/// This provider is used to let us implement any type of form decoding we want without
|
||||
/// having to rewrite anything else in the server.
|
||||
/// </summary>
|
||||
public class FormDecoderProvider
|
||||
{
|
||||
private readonly IList<IFormDecoder> _decoders = new List<IFormDecoder>();
|
||||
private IFormDecoder _defaultDecoder;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="contentType">Should contain boundary and type, as in: multipart/form-data; boundary=---------------------------230051238959</param>
|
||||
/// <param name="stream">Stream containing form data.</param>
|
||||
/// <param name="encoding">Encoding used when decoding the stream</param>
|
||||
/// <returns><see cref="HttpInput.Empty"/> if no parser was found.</returns>
|
||||
/// <exception cref="ArgumentException">If stream is null or not readable.</exception>
|
||||
/// <exception cref="InvalidDataException">If stream contents cannot be decoded properly.</exception>
|
||||
public HttpForm Decode(string contentType, Stream stream, Encoding encoding)
|
||||
{
|
||||
if (encoding == null)
|
||||
encoding = Encoding.UTF8;
|
||||
if (stream == null || !stream.CanRead)
|
||||
throw new ArgumentException("Stream is null or not readable.");
|
||||
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
return _defaultDecoder != null
|
||||
? _defaultDecoder.Decode(stream, contentType, encoding)
|
||||
: HttpForm.EmptyForm;
|
||||
}
|
||||
|
||||
//multipart/form-data; boundary=---------------------------230051238959
|
||||
foreach (IFormDecoder decoder in _decoders)
|
||||
{
|
||||
if (decoder.CanParse(contentType))
|
||||
return decoder.Decode(stream, contentType, encoding);
|
||||
}
|
||||
|
||||
return HttpForm.EmptyForm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a decoder.
|
||||
/// </summary>
|
||||
/// <param name="decoder"></param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public void Add(IFormDecoder decoder)
|
||||
{
|
||||
if (decoder == null)
|
||||
throw new ArgumentNullException("decoder");
|
||||
if (_decoders.Contains(decoder))
|
||||
return;
|
||||
|
||||
_decoders.Add(decoder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of added decoders.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get { return _decoders.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use with care.
|
||||
/// </summary>
|
||||
public IList<IFormDecoder> Decoders
|
||||
{
|
||||
get { return _decoders; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decoder used for unknown content types.
|
||||
/// </summary>
|
||||
public IFormDecoder DefaultDecoder
|
||||
{
|
||||
get { return _defaultDecoder; }
|
||||
set { _defaultDecoder = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,318 +0,0 @@
|
||||
//
|
||||
// System.Web.IHttpRequest.cs
|
||||
//
|
||||
//
|
||||
// Author:
|
||||
// Miguel de Icaza (miguel@novell.com)
|
||||
// Gonzalo Paniagua Javier (gonzalo@novell.com)
|
||||
//
|
||||
|
||||
//
|
||||
// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace HttpServer.FormDecoders
|
||||
{
|
||||
class HttpMultipart
|
||||
{
|
||||
/// <summary>Represents a field in a multipart form</summary>
|
||||
public class Element
|
||||
{
|
||||
public string ContentType;
|
||||
public string Name;
|
||||
public string Filename;
|
||||
public long Start;
|
||||
public long Length;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " +
|
||||
Start + ", Length " + Length;
|
||||
}
|
||||
}
|
||||
|
||||
readonly Stream _data;
|
||||
readonly string _boundary;
|
||||
readonly byte[] _boundaryBytes;
|
||||
readonly byte[] _buffer;
|
||||
bool _eof;
|
||||
readonly Encoding _encoding;
|
||||
readonly StringBuilder _sb;
|
||||
|
||||
const byte Lf = (byte)'\n', Cr = (byte)'\r';
|
||||
|
||||
// See RFC 2046
|
||||
// In the case of multipart entities, in which one or more different
|
||||
// sets of data are combined in a single body, a "multipart" media type
|
||||
// field must appear in the entity's header. The body must then contain
|
||||
// one or more body parts, each preceded by a boundary delimiter line,
|
||||
// and the last one followed by a closing boundary delimiter line.
|
||||
// After its boundary delimiter line, each body part then consists of a
|
||||
// header area, a blank line, and a body area. Thus a body part is
|
||||
// similar to an RFC 822 message in syntax, but different in meaning.
|
||||
|
||||
public HttpMultipart(Stream data, string boundry, Encoding encoding)
|
||||
{
|
||||
_data = data;
|
||||
_boundary = boundry;
|
||||
_boundaryBytes = encoding.GetBytes(boundry);
|
||||
_buffer = new byte[_boundaryBytes.Length + 2]; // CRLF or '--'
|
||||
_encoding = encoding;
|
||||
_sb = new StringBuilder();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
string ReadLine()
|
||||
{
|
||||
// CRLF or Lf are ok as line endings.
|
||||
bool gotCr = false;
|
||||
_sb.Length = 0;
|
||||
while (true)
|
||||
{
|
||||
int b = _data.ReadByte();
|
||||
if (b == -1)
|
||||
return null;
|
||||
|
||||
if (b == Lf)
|
||||
break;
|
||||
|
||||
gotCr = (b == Cr);
|
||||
_sb.Append((char)b);
|
||||
}
|
||||
|
||||
if (gotCr)
|
||||
_sb.Length--;
|
||||
|
||||
return _sb.ToString();
|
||||
|
||||
}
|
||||
|
||||
static string GetContentDispositionAttribute(string l, string name)
|
||||
{
|
||||
int idx = l.IndexOf(name + "=\"");
|
||||
if (idx < 0)
|
||||
return null;
|
||||
int begin = idx + name.Length + "=\"".Length;
|
||||
int end = l.IndexOf('"', begin);
|
||||
if (end < 0)
|
||||
return null;
|
||||
if (begin == end)
|
||||
return "";
|
||||
return l.Substring(begin, end - begin);
|
||||
}
|
||||
|
||||
string GetContentDispositionAttributeWithEncoding(string l, string name)
|
||||
{
|
||||
int idx = l.IndexOf(name + "=\"");
|
||||
if (idx < 0)
|
||||
return null;
|
||||
int begin = idx + name.Length + "=\"".Length;
|
||||
int end = l.IndexOf('"', begin);
|
||||
if (end < 0)
|
||||
return null;
|
||||
if (begin == end)
|
||||
return "";
|
||||
|
||||
string temp = l.Substring(begin, end - begin);
|
||||
byte[] source = new byte[temp.Length];
|
||||
for (int i = temp.Length - 1; i >= 0; i--)
|
||||
source[i] = (byte)temp[i];
|
||||
|
||||
return _encoding.GetString(source);
|
||||
}
|
||||
|
||||
bool ReadBoundary()
|
||||
{
|
||||
try
|
||||
{
|
||||
string line = ReadLine();
|
||||
while (line == "")
|
||||
line = ReadLine();
|
||||
if (line[0] != '-' || line[1] != '-')
|
||||
return false;
|
||||
|
||||
if (!line.EndsWith(_boundary, false, CultureInfo.InvariantCulture))
|
||||
return true;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{}
|
||||
catch (ObjectDisposedException)
|
||||
{}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
string ReadHeaders()
|
||||
{
|
||||
string s = ReadLine();
|
||||
if (s == "")
|
||||
return null;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static bool CompareBytes(byte[] orig, byte[] other)
|
||||
{
|
||||
for (int i = orig.Length - 1; i >= 0; i--)
|
||||
if (orig[i] != other[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
long MoveToNextBoundary()
|
||||
{
|
||||
long retval = 0;
|
||||
bool gotCr = false;
|
||||
|
||||
int state = 0;
|
||||
int c = _data.ReadByte();
|
||||
while (true)
|
||||
{
|
||||
if (c == -1)
|
||||
return -1;
|
||||
|
||||
if (state == 0 && c == Lf)
|
||||
{
|
||||
retval = _data.Position - 1;
|
||||
if (gotCr)
|
||||
retval--;
|
||||
state = 1;
|
||||
c = _data.ReadByte();
|
||||
}
|
||||
else if (state == 0)
|
||||
{
|
||||
gotCr = (c == Cr);
|
||||
c = _data.ReadByte();
|
||||
}
|
||||
else if (state == 1 && c == '-')
|
||||
{
|
||||
c = _data.ReadByte();
|
||||
if (c == -1)
|
||||
return -1;
|
||||
|
||||
if (c != '-')
|
||||
{
|
||||
state = 0;
|
||||
gotCr = false;
|
||||
continue; // no ReadByte() here
|
||||
}
|
||||
|
||||
int nread = _data.Read(_buffer, 0, _buffer.Length);
|
||||
int bl = _buffer.Length;
|
||||
if (nread != bl)
|
||||
return -1;
|
||||
|
||||
if (!CompareBytes(_boundaryBytes, _buffer))
|
||||
{
|
||||
state = 0;
|
||||
_data.Position = retval + 2;
|
||||
if (gotCr)
|
||||
{
|
||||
_data.Position++;
|
||||
gotCr = false;
|
||||
}
|
||||
c = _data.ReadByte();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_buffer[bl - 2] == '-' && _buffer[bl - 1] == '-')
|
||||
{
|
||||
_eof = true;
|
||||
}
|
||||
else if (_buffer[bl - 2] != Cr || _buffer[bl - 1] != Lf)
|
||||
{
|
||||
state = 0;
|
||||
_data.Position = retval + 2;
|
||||
if (gotCr)
|
||||
{
|
||||
_data.Position++;
|
||||
gotCr = false;
|
||||
}
|
||||
c = _data.ReadByte();
|
||||
continue;
|
||||
}
|
||||
_data.Position = retval + 2;
|
||||
if (gotCr)
|
||||
_data.Position++;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// state == 1
|
||||
state = 0; // no ReadByte() here
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
public Element ReadNextElement()
|
||||
{
|
||||
if (_eof || ReadBoundary())
|
||||
return null;
|
||||
|
||||
Element elem = new Element();
|
||||
string header;
|
||||
while ((header = ReadHeaders()) != null)
|
||||
{
|
||||
if (header.StartsWith("Content-Disposition:", true, CultureInfo.InvariantCulture))
|
||||
{
|
||||
elem.Name = GetContentDispositionAttribute(header, "name");
|
||||
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
|
||||
}
|
||||
else if (header.StartsWith("Content-Type:", true, CultureInfo.InvariantCulture))
|
||||
{
|
||||
elem.ContentType = header.Substring("Content-Type:".Length).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
long start = _data.Position;
|
||||
elem.Start = start;
|
||||
long pos = MoveToNextBoundary();
|
||||
if (pos == -1)
|
||||
return null;
|
||||
|
||||
elem.Length = pos - start;
|
||||
return elem;
|
||||
}
|
||||
|
||||
static string StripPath(string path)
|
||||
{
|
||||
if (path == null || path.Length == 0)
|
||||
return path;
|
||||
|
||||
if (path.IndexOf(":\\") != 1)
|
||||
return path;
|
||||
return path.Substring(path.LastIndexOf("\\") + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer.FormDecoders
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for form content decoders.
|
||||
/// </summary>
|
||||
public interface IFormDecoder
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream containing the content</param>
|
||||
/// <param name="contentType">Content type (with any additional info like boundry). Content type is always supplied in lower case</param>
|
||||
/// <param name="encoding">Stream enconding</param>
|
||||
/// <returns>A http form, or null if content could not be parsed.</returns>
|
||||
/// <exception cref="InvalidDataException">If contents in the stream is not valid input data.</exception>
|
||||
HttpForm Decode(Stream stream, string contentType, Encoding encoding);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the decoder can handle the mime type
|
||||
/// </summary>
|
||||
/// <param name="contentType">Content type (with any additional info like boundry). Content type is always supplied in lower case.</param>
|
||||
/// <returns>True if the decoder can parse the specified content type</returns>
|
||||
bool CanParse(string contentType);
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer.FormDecoders
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// http://www.faqs.org/rfcs/rfc1867.html
|
||||
/// </remarks>
|
||||
public class MultipartDecoder : IFormDecoder
|
||||
{
|
||||
/// <summary>
|
||||
/// multipart/form-data
|
||||
/// </summary>
|
||||
public const string MimeType = "multipart/form-data";
|
||||
|
||||
/// <summary>
|
||||
/// form-data
|
||||
/// </summary>
|
||||
public const string FormData = "form-data";
|
||||
#region IFormDecoder Members
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream containing the content</param>
|
||||
/// <param name="contentType">Content type (with any additional info like boundry). Content type is always supplied in lower case</param>
|
||||
/// <param name="encoding">Stream enconding</param>
|
||||
/// <returns>A http form, or null if content could not be parsed.</returns>
|
||||
/// <exception cref="InvalidDataException">If contents in the stream is not valid input data.</exception>
|
||||
/// <exception cref="ArgumentNullException">If any parameter is null</exception>
|
||||
public HttpForm Decode(Stream stream, string contentType, Encoding encoding)
|
||||
{
|
||||
if(stream == null)
|
||||
throw new ArgumentNullException("stream");
|
||||
if(string.IsNullOrEmpty(contentType))
|
||||
throw new ArgumentNullException("contentType");
|
||||
if(encoding == null)
|
||||
throw new ArgumentNullException("encoding");
|
||||
|
||||
if(!CanParse(contentType))
|
||||
throw new InvalidOperationException("Cannot parse contentType: " + contentType);
|
||||
|
||||
//multipart/form-data, boundary=AaB03x
|
||||
int pos = contentType.IndexOf("=");
|
||||
if (pos == -1)
|
||||
throw new InvalidDataException("Missing boundry in content type.");
|
||||
|
||||
string boundry = contentType.Substring(pos + 1).Trim();
|
||||
HttpMultipart multipart = new HttpMultipart(stream, boundry, encoding);
|
||||
|
||||
HttpForm form = new HttpForm();
|
||||
|
||||
HttpMultipart.Element element;
|
||||
while ((element = multipart.ReadNextElement()) != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(element.Name))
|
||||
throw new InvalidDataException("Error parsing request. Missing value name.\nElement: " + element);
|
||||
|
||||
if(!string.IsNullOrEmpty(element.Filename))
|
||||
{
|
||||
if (string.IsNullOrEmpty(element.ContentType))
|
||||
throw new InvalidDataException("Error parsing request. Value '" + element.Name + "' lacks a content type.");
|
||||
|
||||
// Read the file data
|
||||
byte[] buffer = new byte[element.Length];
|
||||
stream.Seek(element.Start, SeekOrigin.Begin);
|
||||
stream.Read(buffer, 0, (int) element.Length);
|
||||
|
||||
// Generate a filename
|
||||
string filename = element.Filename;
|
||||
string internetCache = Environment.GetFolderPath(Environment.SpecialFolder.InternetCache);
|
||||
// if the internet path doesn't exist, assume mono and /var/tmp
|
||||
string path = string.IsNullOrEmpty(internetCache)
|
||||
? Path.Combine("var", "tmp")
|
||||
: Path.Combine(internetCache.Replace("\\\\", "\\"), "tmp");
|
||||
element.Filename = Path.Combine(path, Math.Abs(element.Filename.GetHashCode()) + ".tmp");
|
||||
|
||||
// If the file exists generate a new filename
|
||||
while(File.Exists(element.Filename))
|
||||
element.Filename = Path.Combine(path, Math.Abs(element.Filename.GetHashCode() + 1) + ".tmp");
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
File.WriteAllBytes(element.Filename, buffer);
|
||||
form.AddFile(new HttpFile(element.Name, element.Filename, element.ContentType, filename));
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] buffer = new byte[element.Length];
|
||||
stream.Seek(element.Start, SeekOrigin.Begin);
|
||||
stream.Read(buffer, 0, (int)element.Length);
|
||||
form.Add(element.Name, encoding.GetString(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the decoder can handle the mime type
|
||||
/// </summary>
|
||||
/// <param name="contentType">Content type (with any additional info like boundry). Content type is always supplied in lower case.</param>
|
||||
/// <returns>True if the decoder can parse the specified content type</returns>
|
||||
public bool CanParse(string contentType)
|
||||
{
|
||||
return contentType.StartsWith(MimeType, true, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer.FormDecoders
|
||||
{
|
||||
/// <summary>
|
||||
/// Can handle application/x-www-form-urlencoded
|
||||
/// </summary>
|
||||
public class UrlDecoder : IFormDecoder
|
||||
{
|
||||
#region IFormDecoder Members
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream containing the content</param>
|
||||
/// <param name="contentType">Content type (with any additional info like boundry). Content type is always supplied in lower case</param>
|
||||
/// <param name="encoding">Stream encoding</param>
|
||||
/// <returns>
|
||||
/// A HTTP form, or null if content could not be parsed.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidDataException">If contents in the stream is not valid input data.</exception>
|
||||
public HttpForm Decode(Stream stream, string contentType, Encoding encoding)
|
||||
{
|
||||
if (stream == null || stream.Length == 0)
|
||||
return null;
|
||||
if (!CanParse(contentType))
|
||||
return null;
|
||||
if (encoding == null)
|
||||
encoding = Encoding.UTF8;
|
||||
|
||||
try
|
||||
{
|
||||
StreamReader reader = new StreamReader(stream, encoding);
|
||||
return new HttpForm(HttpHelper.ParseQueryString(reader.ReadToEnd()));
|
||||
}
|
||||
catch(ArgumentException err)
|
||||
{
|
||||
throw new InvalidDataException(err.Message, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the decoder can handle the mime type
|
||||
/// </summary>
|
||||
/// <param name="contentType">Content type (with any additional info like boundry). Content type is always supplied in lower case.</param>
|
||||
/// <returns>True if the decoder can parse the specified content type</returns>
|
||||
public bool CanParse(string contentType)
|
||||
{
|
||||
return !string.IsNullOrEmpty(contentType) && contentType.StartsWith("application/x-www-form-urlencoded", true, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace HttpServer.FormDecoders
|
||||
{
|
||||
/// <summary>
|
||||
/// This decoder converts XML documents to form items.
|
||||
/// Each element becomes a subitem in the form, and each attribute becomes an item.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// // xml: <hello id="1">something<world id="2">data</world></hello>
|
||||
/// // result:
|
||||
/// // form["hello"].Value = "something"
|
||||
/// // form["hello"]["id"].Value = 1
|
||||
/// // form["hello"]["world]["id"].Value = 1
|
||||
/// // form["hello"]["world"].Value = "data"
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// The original xml document is stored in form["__xml__"].Value.
|
||||
/// </remarks>
|
||||
public class XmlDecoder : IFormDecoder
|
||||
{
|
||||
#region IFormDecoder Members
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream containing the content</param>
|
||||
/// <param name="contentType">Content type (with any additional info like boundry). Content type is always supplied in lower case</param>
|
||||
/// <param name="encoding">Stream encoding</param>
|
||||
/// Note: contentType and encoding are not used?
|
||||
/// <returns>A http form, or null if content could not be parsed.</returns>
|
||||
/// <exception cref="InvalidDataException"></exception>
|
||||
public HttpForm Decode(Stream stream, string contentType, Encoding encoding)
|
||||
{
|
||||
if (stream == null || stream.Length == 0)
|
||||
return null;
|
||||
if (!CanParse(contentType))
|
||||
return null;
|
||||
if (encoding == null)
|
||||
encoding = Encoding.UTF8;
|
||||
|
||||
HttpForm form = new HttpForm();
|
||||
|
||||
using (TextReader reader = new StreamReader(stream, encoding))
|
||||
{
|
||||
// let's start with saving the raw xml
|
||||
form.Add("__xml__", reader.ReadToEnd());
|
||||
|
||||
try
|
||||
{
|
||||
// Now let's process the data.
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.LoadXml(form["__xml__"].Value);
|
||||
XmlNode child = doc.FirstChild;
|
||||
// Skip to next node if the node is an XML Declaration
|
||||
if (child.NodeType == XmlNodeType.XmlDeclaration)
|
||||
child = child.NextSibling;
|
||||
TraverseNode(form, child);
|
||||
}
|
||||
catch (XmlException err)
|
||||
{
|
||||
throw new InvalidDataException("Failed to traverse XML", err);
|
||||
}
|
||||
}
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursive function that will go through an xml element and store it's content
|
||||
/// to the form item.
|
||||
/// </summary>
|
||||
/// <param name="item">(parent) Item in form that content should be added to.</param>
|
||||
/// <param name="node">Node that should be parsed.</param>
|
||||
public void TraverseNode(IHttpInput item, XmlNode node)
|
||||
{
|
||||
// Add text node content to previous item
|
||||
if (node.Name == "#text")
|
||||
{
|
||||
HttpInputItem formItem = item as HttpInputItem;
|
||||
if (formItem != null)
|
||||
{
|
||||
formItem.Add(node.InnerText.Trim());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string name = node.Name.ToLower();
|
||||
item.Add(name, node.Value);
|
||||
IHttpInput myItem = item[name];
|
||||
|
||||
if (node.Attributes != null)
|
||||
foreach (XmlAttribute attribute in node.Attributes)
|
||||
myItem.Add(attribute.Name.ToLower(), attribute.Value);
|
||||
|
||||
foreach (XmlNode childNode in node.ChildNodes)
|
||||
TraverseNode(myItem, childNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the decoder can handle the mime type
|
||||
/// </summary>
|
||||
/// <param name="contentType">Content type (with any additional info like boundry). Content type is always supplied in lower case.</param>
|
||||
/// <returns>True if the decoder can parse the specified content type</returns>
|
||||
public bool CanParse(string contentType)
|
||||
{
|
||||
return !string.IsNullOrEmpty(contentType) && contentType.StartsWith("text/xml", true, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers making it easier to work with forms.
|
||||
/// </summary>
|
||||
/// <seealso cref="ObjectForm"/>
|
||||
public static class FormHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to let the website use different JavaScript libraries.
|
||||
/// Default is <see cref="Implementations.PrototypeImp"/>
|
||||
/// </summary>
|
||||
public static JavascriptHelperImplementation JSImplementation = WebHelper.JSImplementation;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a <form> tag.
|
||||
/// </summary>
|
||||
/// <param name="id">name of form</param>
|
||||
/// <param name="action">action to invoke on submit</param>
|
||||
/// <param name="isAjax">form should be posted as Ajax</param>
|
||||
/// <returns>HTML code</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // without options
|
||||
/// WebHelper.FormStart("frmLogin", "/user/login", Request.IsAjax);
|
||||
///
|
||||
/// // with options
|
||||
/// WebHelper.FormStart("frmLogin", "/user/login", Request.IsAjax, "style", "display:inline", "class", "greenForm");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="options">HTML attributes or JavaScript options.</param>
|
||||
/// <remarks>Method will ALWAYS be POST.</remarks>
|
||||
/// <exception cref="ArgumentException">options must consist of name, value, name, value</exception>
|
||||
public static string Start(string id, string action, bool isAjax, params string[] options)
|
||||
{
|
||||
Check.Require(id, "id");
|
||||
Check.NotEmpty(action, "action");
|
||||
if (options == null || options.Length % 2 != 0)
|
||||
throw new ArgumentException("options must consist of name, value, name, value");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("<form action=\"");
|
||||
sb.Append(action);
|
||||
sb.Append("\"");
|
||||
|
||||
if (isAjax)
|
||||
{
|
||||
string onsubmit = JSImplementation.AjaxFormOnSubmit(options) + "return false;";
|
||||
WebHelper.GenerateHtmlAttributes(sb, options, new[]{"onsubmit", onsubmit, "id", id, "method", "post"});
|
||||
}
|
||||
else
|
||||
WebHelper.GenerateHtmlAttributes(sb, options, new[]{"id", id, "method", "post"});
|
||||
|
||||
sb.Append(">");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a select list with the values in a collection.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the SELECT-tag</param>
|
||||
/// <param name="collection">collection used to generate options.</param>
|
||||
/// <param name="getIdTitle">delegate used to return id and title from objects.</param>
|
||||
/// <param name="selectedValue">value that should be marked as selected.</param>
|
||||
/// <param name="firstEmpty">First row should contain an empty value.</param>
|
||||
/// <returns>string containing a SELECT-tag.</returns>
|
||||
/// <seealso cref="GetIdTitle"/>
|
||||
public static string Select(string name, IEnumerable collection, GetIdTitle getIdTitle, object selectedValue, bool firstEmpty)
|
||||
{
|
||||
return Select(name, name, collection, getIdTitle, selectedValue, firstEmpty);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a select list with the values in a collection.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the SELECT-tag</param>
|
||||
/// <param name="id">Id of the SELECT-tag</param>
|
||||
/// <param name="collection">collection used to generate options.</param>
|
||||
/// <param name="getIdTitle">delegate used to return id and title from objects.</param>
|
||||
/// <param name="selectedValue">value that should be marked as selected.</param>
|
||||
/// <param name="firstEmpty">First row should contain an empty value.</param>
|
||||
/// <returns>string containing a SELECT-tag.</returns>
|
||||
/// <seealso cref="GetIdTitle"/>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Class that is going to be used in a SELECT-tag.
|
||||
/// public class User
|
||||
/// {
|
||||
/// private readonly string _realName;
|
||||
/// private readonly int _id;
|
||||
/// public User(int id, string realName)
|
||||
/// {
|
||||
/// _id = id;
|
||||
/// _realName = realName;
|
||||
/// }
|
||||
/// public string RealName
|
||||
/// {
|
||||
/// get { return _realName; }
|
||||
/// }
|
||||
///
|
||||
/// public int Id
|
||||
/// {
|
||||
/// get { return _id; }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Using an inline delegate to generate the select list
|
||||
/// public void UserInlineDelegate()
|
||||
/// {
|
||||
/// List<User> items = new List<User>();
|
||||
/// items.Add(new User(1, "adam"));
|
||||
/// items.Add(new User(2, "bertial"));
|
||||
/// items.Add(new User(3, "david"));
|
||||
/// string htmlSelect = Select("users", "users", items, delegate(object o, out object id, out object value)
|
||||
/// {
|
||||
/// User user = (User)o;
|
||||
/// id = user.Id;
|
||||
/// value = user.RealName;
|
||||
/// }, 2, true);
|
||||
/// }
|
||||
///
|
||||
/// // Using an method as delegate to generate the select list.
|
||||
/// public void UseExternalDelegate()
|
||||
/// {
|
||||
/// List<User> items = new List<User>();
|
||||
/// items.Add(new User(1, "adam"));
|
||||
/// items.Add(new User(2, "bertial"));
|
||||
/// items.Add(new User(3, "david"));
|
||||
/// string htmlSelect = Select("users", "users", items, UserOptions, 1, true);
|
||||
/// }
|
||||
///
|
||||
/// // delegate returning id and title
|
||||
/// public static void UserOptions(object o, out object id, out object title)
|
||||
/// {
|
||||
/// User user = (User)o;
|
||||
/// id = user.Id;
|
||||
/// value = user.RealName;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <exception cref="ArgumentNullException"><c>name</c>, <c>id</c>, <c>collection</c> or <c>getIdTitle</c> is null.</exception>
|
||||
public static string Select(string name, string id, IEnumerable collection, GetIdTitle getIdTitle, object selectedValue, bool firstEmpty)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException("name");
|
||||
if (id == null)
|
||||
throw new ArgumentNullException("id");
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException("collection");
|
||||
if (getIdTitle == null)
|
||||
throw new ArgumentNullException("getIdTitle");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("<select name=\"" + name + "\" id=\"" + id + "\">");
|
||||
Options(sb, collection, getIdTitle, selectedValue, firstEmpty);
|
||||
sb.AppendLine("</select>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a select list with the values in a collection.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the SELECT-tag</param>
|
||||
/// <param name="id">Id of the SELECT-tag</param>
|
||||
/// <param name="collection">collection used to generate options.</param>
|
||||
/// <param name="getIdTitle">delegate used to return id and title from objects.</param>
|
||||
/// <param name="selectedValue">value that should be marked as selected.</param>
|
||||
/// <param name="firstEmpty">First row should contain an empty value.</param>
|
||||
/// <param name="htmlAttributes">name, value collection of extra HTML attributes.</param>
|
||||
/// <returns>string containing a SELECT-tag.</returns>
|
||||
/// <seealso cref="GetIdTitle"/>
|
||||
/// <exception cref="ArgumentNullException"><c>name</c>, <c>id</c>, <c>collection</c> or <c>getIdTitle</c> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Invalid HTML attribute list.</exception>
|
||||
public static string Select(string name, string id, IEnumerable collection, GetIdTitle getIdTitle, object selectedValue, bool firstEmpty, params string[] htmlAttributes)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException("name");
|
||||
if (id == null)
|
||||
throw new ArgumentNullException("id");
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException("collection");
|
||||
if (getIdTitle == null)
|
||||
throw new ArgumentNullException("getIdTitle");
|
||||
if (htmlAttributes != null && (htmlAttributes.Length % 2) != 0)
|
||||
throw new ArgumentException("Invalid HTML attribute list.");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("<select name=\"" + name + "\" id=\"" + id + "\"");
|
||||
WebHelper.GenerateHtmlAttributes(sb, htmlAttributes);
|
||||
sb.AppendLine(">");
|
||||
Options(sb, collection, getIdTitle, selectedValue, firstEmpty);
|
||||
sb.AppendLine("</select>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of HTML options
|
||||
/// </summary>
|
||||
/// <param name="collection">collection used to generate options.</param>
|
||||
/// <param name="getIdTitle">delegate used to return id and title from objects.</param>
|
||||
/// <param name="selectedValue">value that should be marked as selected.</param>
|
||||
/// <param name="firstEmpty">First row should contain an empty value.</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"><c>collection</c> or <c>getIdTitle</c> is null.</exception>
|
||||
public static string Options(IEnumerable collection, GetIdTitle getIdTitle, object selectedValue, bool firstEmpty)
|
||||
{
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException("collection");
|
||||
if (getIdTitle == null)
|
||||
throw new ArgumentNullException("getIdTitle");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Options(sb, collection, getIdTitle, selectedValue, firstEmpty);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <exception cref="ArgumentNullException"><c>sb</c> is null.</exception>
|
||||
private static void Options(StringBuilder sb, IEnumerable collection, GetIdTitle getIdTitle, object selectedValue, bool firstEmpty)
|
||||
{
|
||||
if (sb == null)
|
||||
throw new ArgumentNullException("sb");
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException("collection");
|
||||
if (getIdTitle == null)
|
||||
throw new ArgumentNullException("getIdTitle");
|
||||
|
||||
if (firstEmpty)
|
||||
sb.AppendLine("<option value=\"\"> </option>");
|
||||
|
||||
|
||||
foreach (object o in collection)
|
||||
{
|
||||
object value;
|
||||
string title;
|
||||
getIdTitle(o, out value, out title);
|
||||
sb.Append("<option value=\"");
|
||||
if (value != null)
|
||||
sb.Append(value);
|
||||
sb.Append("\"");
|
||||
|
||||
if (value != null && selectedValue != null && value.Equals(selectedValue))
|
||||
sb.Append(" selected=\"selected\"");
|
||||
|
||||
sb.Append(">");
|
||||
|
||||
if (title == null)
|
||||
sb.AppendLine(" </option>");
|
||||
else
|
||||
{
|
||||
sb.Append(title);
|
||||
sb.AppendLine("</option>");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a check box.
|
||||
/// </summary>
|
||||
/// <param name="name">element name</param>
|
||||
/// <param name="value">element value</param>
|
||||
/// <param name="isChecked">determines if the check box is selected or not. This is done differently depending on the
|
||||
/// type of variable. A boolean simply triggers checked or not, all other types are compared with "value" to determine if
|
||||
/// the box is checked or not. </param>
|
||||
/// <param name="htmlAttributes">a list with additional attributes (name, value, name, value).</param>
|
||||
/// <returns>a generated radio button</returns>
|
||||
public static string CheckBox(string name, object value, object isChecked, params string[] htmlAttributes)
|
||||
{
|
||||
return CheckBox(name, null, value, isChecked, htmlAttributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a check box.
|
||||
/// </summary>
|
||||
/// <param name="name">element name</param>
|
||||
/// <param name="id">element id</param>
|
||||
/// <param name="value">element value</param>
|
||||
/// <param name="isChecked">determines if the check box is selected or not. This is done differently depending on the
|
||||
/// type of variable. A boolean simply triggers checked or not, all other types are compared with "value" to determine if
|
||||
/// the box is checked or not. </param>
|
||||
/// <param name="htmlAttributes">a list with additional attributes (name, value, name, value).</param>
|
||||
/// <returns>a generated radio button</returns>
|
||||
/// <remarks>
|
||||
/// value in your business object. (check box will be selected if it matches the element value)
|
||||
/// </remarks>
|
||||
public static string CheckBox(string name, string id, object value, object isChecked, params string[] htmlAttributes)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("<input type=\"checkbox\" name=\"");
|
||||
sb.Append(name);
|
||||
sb.Append("\"");
|
||||
if (id != null)
|
||||
{
|
||||
sb.Append(" id=\"");
|
||||
sb.Append(id);
|
||||
sb.Append("\"");
|
||||
}
|
||||
|
||||
sb.Append(" value=\"");
|
||||
sb.Append(value);
|
||||
sb.Append("\"");
|
||||
|
||||
if (isChecked is bool && (bool)isChecked)
|
||||
sb.Append("checked=\"checked\"");
|
||||
else if (isChecked == value)
|
||||
sb.Append("checked=\"checked\"");
|
||||
|
||||
WebHelper.GenerateHtmlAttributes(sb, htmlAttributes);
|
||||
sb.Append("/>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a check box.
|
||||
/// </summary>
|
||||
/// <param name="name">element name</param>
|
||||
/// <param name="id">element id</param>
|
||||
/// <param name="isChecked">determines if the check box is selected or not. This is done differently depending on the
|
||||
/// type of variable. A boolean simply triggers checked or not, all other types are compared with "value" to determine if
|
||||
/// the box is checked or not. </param>
|
||||
/// <param name="htmlAttributes">a list with additional attributes (name, value, name, value).</param>
|
||||
/// <returns>a generated radio button</returns>
|
||||
/// <remarks>will set value to "1".</remarks>
|
||||
public static string CheckBox(string name, string id, object isChecked, params string[] htmlAttributes)
|
||||
{
|
||||
return CheckBox(name, id, 1, isChecked, htmlAttributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RadioButton.
|
||||
/// </summary>
|
||||
/// <param name="name">element name</param>
|
||||
/// <param name="value">element value</param>
|
||||
/// <param name="isSelected">determines if the radio button is selected or not. This is done differently depending on the
|
||||
/// type of variable. A boolean simply triggers checked or not, all other types are compared with "value" to determine if
|
||||
/// the box is checked or not. </param>
|
||||
/// <param name="htmlAttributes">a list with additional attributes (name, value, name, value).</param>
|
||||
/// <returns>a generated radio button</returns>
|
||||
public static string RadioButton(string name, object value, object isSelected, params string[] htmlAttributes)
|
||||
{
|
||||
return RadioButton(name, null, value, isSelected, htmlAttributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RadioButton.
|
||||
/// </summary>
|
||||
/// <param name="name">element name</param>
|
||||
/// <param name="id">element id</param>
|
||||
/// <param name="value">element value</param>
|
||||
/// <param name="isSelected">determines if the radio button is selected or not. This is done differently depending on the
|
||||
/// type of variable. A boolean simply triggers checked or not, all other types are compared with "value" to determine if
|
||||
/// the box is checked or not. </param>
|
||||
/// <param name="htmlAttributes">a list with additional attributes (name, value, name, value).</param>
|
||||
/// <returns>a generated radio button</returns>
|
||||
public static string RadioButton(string name, string id, object value, object isSelected, params string[] htmlAttributes)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("<input type=\"radio\" name=\"");
|
||||
sb.Append(name);
|
||||
sb.Append("\"");
|
||||
if (id != null)
|
||||
{
|
||||
sb.Append(" id=\"");
|
||||
sb.Append(id);
|
||||
sb.Append("\"");
|
||||
}
|
||||
sb.Append(" value=\"");
|
||||
sb.Append(value);
|
||||
sb.Append("\"");
|
||||
|
||||
if (isSelected is bool && (bool)isSelected)
|
||||
sb.Append("checked=\"checked\"");
|
||||
else if (isSelected.Equals(value))
|
||||
sb.Append("checked=\"checked\"");
|
||||
|
||||
WebHelper.GenerateHtmlAttributes(sb, htmlAttributes);
|
||||
sb.Append("/>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// form close tag
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string End()
|
||||
{
|
||||
return "</form>";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace HttpServer.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate used by <see cref="FormHelper.Select(string, System.Collections.IEnumerable, GetIdTitle, object, bool)"/> to populate select options.
|
||||
/// </summary>
|
||||
/// <param name="obj">current object (for instance a User).</param>
|
||||
/// <param name="id">Text that should be displayed in the value part of a <optiongt;-tag.</param>
|
||||
/// <param name="title">Text shown in the select list.</param>
|
||||
/// <example>
|
||||
/// // Class that is going to be used in a SELECT-tag.
|
||||
/// public class User
|
||||
/// {
|
||||
/// private readonly string _realName;
|
||||
/// private readonly int _id;
|
||||
/// public User(int id, string realName)
|
||||
/// {
|
||||
/// _id = id;
|
||||
/// _realName = realName;
|
||||
/// }
|
||||
/// public string RealName
|
||||
/// {
|
||||
/// get { return _realName; }
|
||||
/// }
|
||||
///
|
||||
/// public int Id
|
||||
/// {
|
||||
/// get { return _id; }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Using an inline delegate to generate the select list
|
||||
/// public void UserInlineDelegate()
|
||||
/// {
|
||||
/// List<User> items = new List<User>();
|
||||
/// items.Add(new User(1, "adam"));
|
||||
/// items.Add(new User(2, "bertial"));
|
||||
/// items.Add(new User(3, "david"));
|
||||
/// string htmlSelect = Select("users", "users", items, delegate(object o, out object id, out object value)
|
||||
/// {
|
||||
/// User user = (User)o;
|
||||
/// id = user.Id;
|
||||
/// value = user.RealName;
|
||||
/// }, 2, true);
|
||||
/// }
|
||||
///
|
||||
/// // Using an method as delegate to generate the select list.
|
||||
/// public void UseExternalDelegate()
|
||||
/// {
|
||||
/// List<User> items = new List<User>();
|
||||
/// items.Add(new User(1, "adam"));
|
||||
/// items.Add(new User(2, "bertial"));
|
||||
/// items.Add(new User(3, "david"));
|
||||
/// string htmlSelect = Select("users", "users", items, UserOptions, 1, true);
|
||||
/// }
|
||||
///
|
||||
/// // delegate returning id and title
|
||||
/// public static void UserOptions(object o, out object id, out object title)
|
||||
/// {
|
||||
/// User user = (User)o;
|
||||
/// id = user.Id;
|
||||
/// value = user.RealName;
|
||||
/// } /// </example>
|
||||
public delegate void GetIdTitle(object obj, out object id, out string title);
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer.Helpers.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// PrototypeJS implementation of the javascript functions.
|
||||
/// </summary>
|
||||
public class PrototypeImp : JavascriptHelperImplementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Requests a url through ajax
|
||||
/// </summary>
|
||||
/// <param name="url">url to fetch. Url is NOT enclosed in quotes by the implementation. You need to do that yourself.</param>
|
||||
/// <param name="options">optional options in format "key, value, key, value", used in JS request object. All keys should end with colon.</param>
|
||||
/// <returns>a link tag</returns>
|
||||
/// <remarks>onclick attribute is used by this method.</remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // plain text
|
||||
/// JSHelper.AjaxRequest("'/user/show/1'");
|
||||
///
|
||||
/// // ajax request using this.href
|
||||
/// string link = "<a href=\"/user/call/1\" onclick=\"" + JSHelper.AjaxRequest("this.href") + "/<call user</a>";
|
||||
/// </code>
|
||||
/// </example>
|
||||
public override string AjaxRequest(string url, params string[] options)
|
||||
{
|
||||
// For each parameter ending with : push it into the Ajax script as a javascript parameter
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("new Ajax.Request(");
|
||||
sb.Append(url);
|
||||
sb.Append(", { ");
|
||||
bool startWithComma = false;
|
||||
if (!Contains(options, "method:"))
|
||||
{
|
||||
sb.Append("method: 'get'");
|
||||
startWithComma = true;
|
||||
}
|
||||
if(!Contains(options, "evalScripts:"))
|
||||
{
|
||||
sb.Append(startWithComma ? ",evalScripts: true" : "evalScripts: true");
|
||||
startWithComma = true;
|
||||
}
|
||||
GenerateOptions(sb, options, startWithComma);
|
||||
sb.Append(" });");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determins if a list of strings contains a specific value
|
||||
/// </summary>
|
||||
/// <param name="options">options to check in</param>
|
||||
/// <param name="value">value to find</param>
|
||||
/// <returns>true if value was found</returns>
|
||||
/// <remarks>case insensitive</remarks>
|
||||
private static bool Contains(IEnumerable<string> options, string value)
|
||||
{
|
||||
foreach (string s in options)
|
||||
{
|
||||
if (string.Compare(value, s, true) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ajax requests that updates an element with
|
||||
/// the fetched content
|
||||
/// </summary>
|
||||
/// <param name="url">URL to fetch. URL is NOT enclosed in quotes by the implementation. You need to do that yourself.</param>
|
||||
/// <param name="targetId">element to update</param>
|
||||
/// <param name="options">options in format "key, value, key, value". All keys should end with colon.</param>
|
||||
/// <returns>A link tag.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// JSHelper.AjaxUpdater("'/user/show/1'", "user", "onsuccess:", "alert('hello');", "asynchronous:", "true");
|
||||
/// </code>
|
||||
/// </example>
|
||||
public override string AjaxUpdater(string url, string targetId, params string[] options)
|
||||
{
|
||||
// For each parameter ending with : push it into the Ajax script as a javascript parameter
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("new Ajax.Updater('");
|
||||
sb.Append(targetId);
|
||||
sb.Append("', ");
|
||||
sb.Append(url);
|
||||
sb.Append(", { ");
|
||||
bool startWithComma = false;
|
||||
if (!Contains(options, "method:"))
|
||||
{
|
||||
sb.Append("method: 'get'");
|
||||
startWithComma = true;
|
||||
}
|
||||
if (!Contains(options, "evalScripts:"))
|
||||
{
|
||||
sb.Append(startWithComma ? ",evalScripts: true" : "evalScripts: true");
|
||||
startWithComma = true;
|
||||
}
|
||||
GenerateOptions(sb, options, startWithComma);
|
||||
sb.Append(" });");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A link that pop ups a Dialog (overlay div)
|
||||
/// </summary>
|
||||
/// <param name="url">URL to contents of dialog</param>
|
||||
/// <param name="title">link title</param>
|
||||
/// <param name="htmlAttributes">name, value, name, value</param>
|
||||
/// <returns>
|
||||
/// A "a"-tag that popups a dialog when clicked
|
||||
/// </returns>
|
||||
/// <remarks><para>Requires Control.Modal found here: http://livepipe.net/projects/control_modal/</para>
|
||||
/// And the following JavaScript (load it in application.js):
|
||||
/// <code>
|
||||
/// Event.observe(window, 'load',
|
||||
/// function() {
|
||||
/// document.getElementsByClassName('modal').each(function(link){ new Control.Modal(link); });
|
||||
/// }
|
||||
/// );
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// WebHelper.DialogLink("/user/show/1", "show user", "onmouseover", "alert('booh!');");
|
||||
/// </example>
|
||||
public override string DialogLink(string url, string title, params string[] htmlAttributes)
|
||||
{
|
||||
return WebHelper.BuildLink(url, title, htmlAttributes, "class", "modal");
|
||||
}
|
||||
|
||||
#region JavascriptImplementation Members
|
||||
|
||||
/// <summary>
|
||||
/// create a modal dialog (usually using DIVs)
|
||||
/// </summary>
|
||||
/// <param name="url">url to fetch</param>
|
||||
/// <param name="title">dialog title</param>
|
||||
/// <param name="options">javascript/html attributes. javascript options ends with colon ':'.</param>
|
||||
/// <returns></returns>
|
||||
public override string CreateDialog(string url, string title, params string[] options)
|
||||
{
|
||||
return string.Format("new Control.Modal('{0}');", url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close a javascript dialog window/div.
|
||||
/// </summary>
|
||||
/// <returns>javascript for closing a dialog.</returns>
|
||||
/// <see cref="DialogLink" />
|
||||
public override string CloseDialog()
|
||||
{
|
||||
return "Control.Modal.close();";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// javascript action that should be added to the "onsubmit" event in the form tag.
|
||||
/// </summary>
|
||||
/// <param name="options">remember to encapsulate strings in ''</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>All javascript option names should end with colon.</remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// JSHelper.AjaxRequest("/user/show/1", "onsuccess:", "$('userInfo').update(result);");
|
||||
/// </code>
|
||||
/// </example>
|
||||
public override string AjaxFormOnSubmit(params string[] options)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("new Ajax.Request(this.action, { parameters: Form.serialize(this)");
|
||||
|
||||
// copy all javascript options
|
||||
string[] theOptions = new string[options.Length + 6];
|
||||
//options.CopyTo(theOptions, 0); todo: is this faster or slower?
|
||||
for (int i = 0; i < options.Length; i += 2 )
|
||||
{
|
||||
if (options[i][options[i].Length-1] == ':')
|
||||
{
|
||||
theOptions[i] = options[i];
|
||||
theOptions[i + 1] = options[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
int index = options.Length;
|
||||
theOptions[index++] = "method:";
|
||||
theOptions[index++] = "'post'";
|
||||
theOptions[index++] = "asynchronous:";
|
||||
theOptions[index++] = "true";
|
||||
theOptions[index++] = "evalScripts:";
|
||||
theOptions[index] = "true";
|
||||
|
||||
GenerateOptions(sb, theOptions, true);
|
||||
sb.Append(" });");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
namespace HttpServer.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Will contain helper functions for javascript.
|
||||
/// </summary>
|
||||
public static class JSHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Requests a url through ajax
|
||||
/// </summary>
|
||||
/// <param name="url">url to fetch. Url is NOT enclosed in quotes by the implementation. You need to do that yourself.</param>
|
||||
/// <param name="options">optional options in format "key, value, key, value", used in JS request object. All keys should end with colon.</param>
|
||||
/// <returns>a link tag</returns>
|
||||
/// <remarks>onclick attribute is used by this method.</remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // plain text
|
||||
/// JSHelper.AjaxRequest("'/user/show/1'");
|
||||
///
|
||||
/// // ajax request using this.href
|
||||
/// string link = "<a href=\"/user/call/1\" onclick=\"" + JSHelper.AjaxRequest("this.href") + "/<call user</a>";
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static string AjaxRequest(string url, params string[] options)
|
||||
{
|
||||
return WebHelper.JSImplementation.AjaxRequest(url, options);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ajax requests that updates an element with
|
||||
/// the fetched content
|
||||
/// </summary>
|
||||
/// <param name="url">url to fetch. Url is NOT enclosed in quotes by the implementation. You need to do that yourself.</param>
|
||||
/// <param name="targetId">element to update</param>
|
||||
/// <param name="options">options in format "key, value, key, value". All keys should end with colon.</param>
|
||||
/// <returns>A link tag.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// JSHelper.AjaxUpdater("'/user/show/1'", "user", "onsuccess:", "alert('hello');", "asynchronous:", "true");
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static string AjaxUpdater(string url, string targetId, params string[] options)
|
||||
{
|
||||
return WebHelper.JSImplementation.AjaxUpdater(url, targetId, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens contents in a dialog window.
|
||||
/// </summary>
|
||||
/// <param name="url">url to contents of dialog</param>
|
||||
/// <param name="title">link title</param>
|
||||
/// <param name="options">name, value, name, value, all parameter names should end with colon.</param>
|
||||
public static string CreateDialog(string url, string title, params string[] options)
|
||||
{
|
||||
return WebHelper.JSImplementation.CreateDialog(url, title, options);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close a javascript dialog window/div.
|
||||
/// </summary>
|
||||
/// <returns>javascript for closing a dialog.</returns>
|
||||
/// <see cref="CreateDialog" />
|
||||
public static string CloseDialog()
|
||||
{
|
||||
return WebHelper.JSImplementation.CloseDialog();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// The object form class takes an object and creates form items for it.
|
||||
/// </summary>
|
||||
public class ObjectForm
|
||||
{
|
||||
private readonly object _object;
|
||||
private readonly string _action;
|
||||
private readonly string _name = string.Empty;
|
||||
private readonly string _method = "post";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectForm"/> class.
|
||||
/// </summary>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="name">form name *and* id.</param>
|
||||
/// <param name="action">action to do when form is posted.</param>
|
||||
/// <param name="obj"></param>
|
||||
public ObjectForm(string action, string name, object obj, string method)
|
||||
: this(name, action, obj)
|
||||
{
|
||||
if (string.IsNullOrEmpty(method))
|
||||
throw new ArgumentNullException("method");
|
||||
|
||||
_method = method.ToLower();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectForm"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">form name *and* id.</param>
|
||||
/// <param name="action">action to do when form is posted.</param>
|
||||
/// <param name="obj">object to get values from</param>
|
||||
public ObjectForm(string action, string name, object obj)
|
||||
: this(action, obj)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentNullException("name");
|
||||
|
||||
_name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectForm"/> class.
|
||||
/// </summary>
|
||||
/// <param name="action">form action.</param>
|
||||
/// <param name="obj">object to get values from.</param>
|
||||
public ObjectForm(string action, object obj)
|
||||
{
|
||||
if (string.IsNullOrEmpty(action))
|
||||
throw new ArgumentNullException("action");
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException("obj");
|
||||
|
||||
_object = obj;
|
||||
_action = action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write out the FORM-tag.
|
||||
/// </summary>
|
||||
/// <returns>generated html code</returns>
|
||||
public string Begin()
|
||||
{
|
||||
return Begin(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writeout the form tag
|
||||
/// </summary>
|
||||
/// <param name="isAjax">form should be posted through ajax.</param>
|
||||
/// <returns>generated html code</returns>
|
||||
public string Begin(bool isAjax)
|
||||
{
|
||||
string ajaxCode;
|
||||
if (isAjax)
|
||||
ajaxCode = "onsubmit=\"" + WebHelper.JSImplementation.AjaxFormOnSubmit() + "\"";
|
||||
else
|
||||
ajaxCode = string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(_name))
|
||||
return string.Format("<form method=\"{0}\" id=\"{1}\" name=\"{1}\" {3} action=\"{2}\">", _method, _name, _action, ajaxCode);
|
||||
else
|
||||
return string.Format("<form method=\"{0}\" action=\"{1}\" {2}>", _method, _action, ajaxCode);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generates a text box.
|
||||
/// </summary>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns>generated html code</returns>
|
||||
public string Tb(string propertyName, params object[] options)
|
||||
{
|
||||
if (options.Length % 2 != 0)
|
||||
throw new ArgumentException("Options must consist of string, object, ... as key/value couple(s).");
|
||||
|
||||
if (options.Length == 0)
|
||||
return string.Format("<input type=\"text\" name=\"{0}[{1}]\" id=\"{0}_{1}\" value=\"{2}\" />", _name, propertyName, GetValue(propertyName));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < options.Length; i += 2)
|
||||
{
|
||||
sb.Append(options[i]);
|
||||
sb.Append("=\"");
|
||||
sb.Append(options[i + 1].ToString());
|
||||
sb.Append("\"");
|
||||
}
|
||||
|
||||
return string.Format("<input type=\"text\" name=\"{0}[{1}]\" id=\"{0}_{1}\" {3} value=\"{2}\" />", _name, propertyName, GetValue(propertyName), sb);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// password box
|
||||
/// </summary>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns>generated html code</returns>
|
||||
public string Pb(string propertyName, params object[] options)
|
||||
{
|
||||
return string.Format("<input type=\"password\" name=\"{0}[{1}]\" id=\"{0}_{1}\" />", _name, propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hiddens the specified property name.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns>generated html code</returns>
|
||||
public string Hidden(string propertyName, params object[] options)
|
||||
{
|
||||
return string.Format("<input type=\"hidden\" name=\"{0}[{1}]\" id=\"{0}_{1}\" value=\"{2}\" />", _name, propertyName, GetValue(propertyName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Labels the specified property name.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">property in object.</param>
|
||||
/// <param name="label">caption</param>
|
||||
/// <returns>generated html code</returns>
|
||||
public string Label(string propertyName, string label)
|
||||
{
|
||||
return string.Format("<label for=\"{0}_{1}\">{2}</label>", _name, propertyName, label);
|
||||
}
|
||||
/// <summary>
|
||||
/// Generate a checkbox
|
||||
/// </summary>
|
||||
/// <param name="propertyName">property in object</param>
|
||||
/// <param name="value">checkbox value</param>
|
||||
/// <param name="options">additional html attributes.</param>
|
||||
/// <returns>generated html code</returns>
|
||||
public string Cb(string propertyName, string value, params object[] options)
|
||||
{
|
||||
string isChecked = string.Empty;
|
||||
|
||||
object o = GetValue(propertyName);
|
||||
if (o.GetType() == typeof(bool) && (bool)o)
|
||||
isChecked = "checked=\"checked\"";
|
||||
else
|
||||
{
|
||||
int i;
|
||||
int.TryParse(o.ToString(), out i);
|
||||
if (i != 0)
|
||||
isChecked = "checked=\"checked\"";
|
||||
}
|
||||
|
||||
return string.Format("<input type=\"hidden\" name=\"{0}\" id=\"{0}_{1}\" value=\"0\" /><input type=\"checkbox\" name=\"{0}[{1}]\" id=\"{0}_{1}\" value=\"{2}\" {3} />", _name, propertyName, value, isChecked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a html select tag
|
||||
/// </summary>
|
||||
/// <param name="propertyName">object property.</param>
|
||||
/// <param name="idColumn">id column</param>
|
||||
/// <param name="titleColumn">The title column.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns></returns>
|
||||
public string Select(string propertyName, string idColumn, string titleColumn, params object[] options)
|
||||
{
|
||||
object o = _object.GetType().GetProperty(propertyName).GetValue(_object, null);
|
||||
return Select(propertyName, o as IEnumerable, idColumn, titleColumn, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects the specified property name.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="idColumn">The id column.</param>
|
||||
/// <param name="titleColumn">The title column.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns></returns>
|
||||
public string Select(string propertyName, IEnumerable items, string idColumn, string titleColumn, params object[] options)
|
||||
{
|
||||
//object o = _object.GetType().GetProperty(propertyName).GetValue(_object, null);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendFormat("<select name=\"{0}[{1}]\">{2}", _name, propertyName, Environment.NewLine);
|
||||
|
||||
if (items != null)
|
||||
foreach (object o1 in items)
|
||||
{
|
||||
sb.AppendFormat("<option value=\"{0}\">{1}</option>{2}",
|
||||
o1.GetType().GetProperty(idColumn).GetValue(o1, null),
|
||||
o1.GetType().GetProperty(titleColumn).GetValue(o1, null), Environment.NewLine);
|
||||
|
||||
}
|
||||
sb.AppendLine("</select>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a submit tag.
|
||||
/// </summary>
|
||||
/// <param name="value">button caption</param>
|
||||
/// <returns>html submit tag</returns>
|
||||
public string Submit(string value)
|
||||
{
|
||||
return string.Format("<input type=\"submit\" value=\"{0}\" />", value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// html end form tag
|
||||
/// </summary>
|
||||
/// <returns>html</returns>
|
||||
public string End()
|
||||
{
|
||||
return "</form>";
|
||||
}
|
||||
|
||||
private string GetValue(string propertyName)
|
||||
{
|
||||
Type type = _object.GetType();
|
||||
PropertyInfo pi = type.GetProperty(propertyName);
|
||||
if (pi == null) throw new ArgumentException("Property " + propertyName + " not found for type " + type);
|
||||
object o = pi.GetValue(_object, null);
|
||||
if (o == null)
|
||||
return string.Empty;
|
||||
else
|
||||
return o.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HttpServer.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Container to bind resource names to assemblies
|
||||
/// </summary>
|
||||
internal class ResourceInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates an instance of <see cref="ResourceInfo"/>
|
||||
/// </summary>
|
||||
/// <param name="uri">The dot seperated uri the resource maps to</param>
|
||||
/// <param name="resourceName">The full resource name</param>
|
||||
/// <param name="assembly">The assembly the resource exists in</param>
|
||||
public ResourceInfo(string resourceName, string uri, Assembly assembly)
|
||||
{
|
||||
Check.NotEmpty(resourceName, "resourceName");
|
||||
Check.NotEmpty(uri, "uri");
|
||||
Check.Require(assembly, "assembly");
|
||||
|
||||
ResourceName = resourceName;
|
||||
Assembly = assembly;
|
||||
Uri = uri;
|
||||
|
||||
int dotIndex = Uri.LastIndexOf('.');
|
||||
if (dotIndex != -1)
|
||||
{
|
||||
Extension = Uri.Substring(dotIndex + 1);
|
||||
ExtensionLessUri = Uri.Substring(0, Uri.Length - Extension.Length - 1);
|
||||
}
|
||||
else
|
||||
Extension = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the assembly the resource resides in
|
||||
/// </summary>
|
||||
public Assembly Assembly { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the full name/path of the assembly
|
||||
/// </summary>
|
||||
public string Uri { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the extension of the resource
|
||||
/// </summary>
|
||||
public string Extension { get; private set; }
|
||||
|
||||
/// <summary>Returns the Uri without extension</summary>
|
||||
public string ExtensionLessUri { get; private set; }
|
||||
|
||||
/// <summary>Retrieves the full path name to the resource file</summary>
|
||||
public string ResourceName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a stream to the resource
|
||||
/// </summary>
|
||||
/// <returns>Null if the resource couldn't be located somehow</returns>
|
||||
public Stream GetStream()
|
||||
{
|
||||
return Assembly.GetManifestResourceStream(ResourceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HttpServer.Helpers
|
||||
{
|
||||
/// <summary>Class to handle loading of resource files</summary>
|
||||
public class ResourceManager
|
||||
{
|
||||
private readonly ILogWriter _logger;
|
||||
|
||||
private readonly Dictionary<string, ResourceInfo> _resources = new Dictionary<string, ResourceInfo>();
|
||||
|
||||
private readonly Dictionary<string, List<ResourceInfo>> _mappedResources = new Dictionary<string, List<ResourceInfo>>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResourceManager"/> class.
|
||||
/// </summary>
|
||||
public ResourceManager()
|
||||
{
|
||||
_logger = NullLogWriter.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResourceManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="writer">logger.</param>
|
||||
public ResourceManager(ILogWriter writer)
|
||||
{
|
||||
_logger = writer ?? NullLogWriter.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads resources from a namespace in the given assembly to an URI
|
||||
/// </summary>
|
||||
/// <param name="toUri">The URI to map the resources to</param>
|
||||
/// <param name="fromAssembly">The assembly in which the resources reside</param>
|
||||
/// <param name="fromNamespace">The namespace from which to load the resources</param>
|
||||
/// <usage>
|
||||
/// <code>
|
||||
/// resourceLoader.LoadResources("/user/", typeof(User).Assembly, "MyLib.Models.User.Views");
|
||||
/// </code>
|
||||
/// Will make the resource MyLib.Models.User.Views.list.Haml accessible via /user/list.haml or /user/list/
|
||||
/// </usage>
|
||||
/// <returns>The amount of loaded files, giving you the possibility of making sure the resources needed gets loaded</returns>
|
||||
/// <exception cref="InvalidOperationException">If a resource has already been mapped to an uri</exception>
|
||||
public int LoadResources(string toUri, Assembly fromAssembly, string fromNamespace)
|
||||
{
|
||||
// We'll save all uri's point seperated to be able to retrieve files with dots in 'em (like /user/oblata.exe which would be mapped to user.oblata.exe)
|
||||
toUri = toUri.ToLower().Replace('/', '.').Trim('.');
|
||||
if (!string.IsNullOrEmpty(toUri))
|
||||
toUri += ".";
|
||||
|
||||
fromNamespace = fromNamespace.ToLower();
|
||||
if (!fromNamespace.EndsWith("."))
|
||||
fromNamespace += ".";
|
||||
|
||||
int added = 0;
|
||||
foreach (string resourceName in fromAssembly.GetManifestResourceNames())
|
||||
{
|
||||
string name = resourceName.ToLower();
|
||||
if (!name.StartsWith(fromNamespace))
|
||||
continue;
|
||||
|
||||
// Create a search name for the resource file, aka just strip the from namespace from resource path and add uri
|
||||
string uri = toUri + name.Substring(fromNamespace.Length);
|
||||
ResourceInfo info = new ResourceInfo(resourceName, uri, fromAssembly);
|
||||
|
||||
// If a resource has previously been added to the exact url we need to throw an exception
|
||||
if(_resources.ContainsKey(info.Uri))
|
||||
throw new InvalidOperationException("A resource has already been mapped to the uri: " + info.Uri);
|
||||
|
||||
List<ResourceInfo> mapped;
|
||||
if(!_mappedResources.TryGetValue(info.ExtensionLessUri + ".*", out mapped))
|
||||
_mappedResources.Add(info.ExtensionLessUri + ".*", new List<ResourceInfo>{info});
|
||||
else
|
||||
mapped.Add(info);
|
||||
|
||||
_resources.Add(info.Uri, info);
|
||||
added++;
|
||||
|
||||
_logger.Write(this, LogPrio.Info, "Resource '" + info.ResourceName + "' loaded to uri: " + uri);
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
private string FormatPath(string path)
|
||||
{
|
||||
return path.ToLower().Replace('/', '.').Replace('\\', '.').Trim('.');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a stream for the specified resource path if loaded otherwise null
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the resource to retrieve a stream for</param>
|
||||
/// <returns>A stream or null if the resource couldn't be found</returns>
|
||||
public Stream GetResourceStream(string path)
|
||||
{
|
||||
ResourceInfo info;
|
||||
if(!_resources.TryGetValue(FormatPath(path), out info))
|
||||
return null;
|
||||
|
||||
return info.GetStream();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch all files from the resource that matches the specified arguments.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the resource to extract</param>
|
||||
/// <returns>
|
||||
/// a list of files if found; or an empty array if no files are found.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException">Search path must end with an asterisk for finding arbitrary files</exception>
|
||||
public string[] GetFiles(string path)
|
||||
{
|
||||
Check.NotEmpty(path, "path");
|
||||
if(!path.EndsWith(".*"))
|
||||
{
|
||||
ResourceInfo info;
|
||||
return _resources.TryGetValue(FormatPath(path), out info) ? new []{path} : new string[]{};
|
||||
}
|
||||
|
||||
List<ResourceInfo> resources;
|
||||
if(!_mappedResources.TryGetValue(FormatPath(path), out resources))
|
||||
return new string[]{};
|
||||
|
||||
path = path.TrimEnd('*');
|
||||
List<string> files = new List<string>();
|
||||
foreach (ResourceInfo info in resources)
|
||||
files.Add(path + info.Extension);
|
||||
|
||||
return files.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch all files from the resource that matches the specified arguments.
|
||||
/// </summary>
|
||||
/// <param name="path">Where the file should reside.</param>
|
||||
/// <param name="filename">Files to check</param>
|
||||
/// <returns>
|
||||
/// a list of files if found; or an empty array if no files are found.
|
||||
/// </returns>
|
||||
public string[] GetFiles(string path, string filename)
|
||||
{
|
||||
Check.NotEmpty(path, "path");
|
||||
Check.NotEmpty(filename, "filename");
|
||||
|
||||
path = path.EndsWith(".") ? path : (path + ".");
|
||||
return GetFiles(path + filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not the loader has an instance of the file requested
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the template/file</param>
|
||||
/// <returns>True if the loader can provide the file</returns>
|
||||
public bool ContainsResource(string filename)
|
||||
{
|
||||
filename = FormatPath(filename);
|
||||
if (filename.EndsWith("*"))
|
||||
return _mappedResources.ContainsKey(filename);
|
||||
|
||||
return _resources.ContainsKey(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,488 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text;
|
||||
using HttpServer.Helpers.Implementations;
|
||||
|
||||
namespace HttpServer.Helpers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Webhelper provides helpers for common tasks in HTML.
|
||||
/// </summary>
|
||||
public static class WebHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to let the website use different javascript libraries.
|
||||
/// Default is <see cref="PrototypeImp"/>
|
||||
/// </summary>
|
||||
public static JavascriptHelperImplementation JSImplementation = new PrototypeImp();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a link that invokes through ajax.
|
||||
/// </summary>
|
||||
/// <param name="url">url to fetch</param>
|
||||
/// <param name="title">link title</param>
|
||||
/// <param name="options">
|
||||
/// optional options in format "key, value, key, value".
|
||||
/// Javascript options starts with ':'.
|
||||
/// </param>
|
||||
/// <returns>a link tag</returns>
|
||||
/// <example>
|
||||
/// WebHelper.AjaxRequest("/users/add/", "Add user", "method:", "post", "onclick", "validate('this');");
|
||||
/// </example>
|
||||
public static string AjaxRequest(string url, string title, params string[] options)
|
||||
{
|
||||
string text = JSImplementation.AjaxRequest("this.href", options) + "return false;";
|
||||
options = JSImplementation.RemoveJavascriptOptions(options);
|
||||
return BuildLink(url, title, options, "onclick", text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a link that updates an element with the fetched ajax content.
|
||||
/// </summary>
|
||||
/// <param name="url">Url to fetch content from</param>
|
||||
/// <param name="title">link title</param>
|
||||
/// <param name="targetId">html element to update with the results of the ajax request.</param>
|
||||
/// <param name="options">optional options in format "key, value, key, value"</param>
|
||||
/// <returns>A link tag.</returns>
|
||||
public static string AjaxUpdater(string url, string title, string targetId, params string[] options)
|
||||
{
|
||||
string text = JSImplementation.AjaxUpdater("this.href", targetId, options) + "return false;";
|
||||
options = JSImplementation.RemoveJavascriptOptions(options);
|
||||
return BuildLink(url, title, options, "onclick", text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A link that pop ups a Dialog (overlay div)
|
||||
/// </summary>
|
||||
/// <param name="url">url to contents of dialog</param>
|
||||
/// <param name="title">link title</param>
|
||||
/// <param name="htmlAttributes">name/value of html attributes.</param>
|
||||
/// <returns>A "a"-tag that popups a dialog when clicked</returns>
|
||||
/// <example>
|
||||
/// WebHelper.DialogLink("/user/show/1", "show user", "onmouseover", "alert('booh!');");
|
||||
/// </example>
|
||||
public static string DialogLink(string url, string title, params string[] htmlAttributes)
|
||||
{
|
||||
return JSImplementation.DialogLink(url, title, htmlAttributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create/Open a dialog box using ajax
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="title"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
public static string CreateDialog(string url, string title, params string[] parameters)
|
||||
{
|
||||
return JSImplementation.CreateDialog(url, title, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close a javascript dialog window/div.
|
||||
/// </summary>
|
||||
/// <returns>javascript for closing a dialog.</returns>
|
||||
/// <see cref="DialogLink"/>
|
||||
public static string CloseDialog()
|
||||
{
|
||||
return JSImplementation.CloseDialog();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <form> tag.
|
||||
/// </summary>
|
||||
/// <param name="name">name of form</param>
|
||||
/// <param name="action">action to invoke on submit</param>
|
||||
/// <param name="isAjax">form should be posted as ajax</param>
|
||||
/// <returns>html code</returns>
|
||||
/// <example>
|
||||
/// WebHelper.FormStart("frmLogin", "/user/login", Request.IsAjax);
|
||||
/// </example>
|
||||
[Obsolete("Moved to FormHelper")]
|
||||
public static string FormStart(string name, string action, bool isAjax)
|
||||
{
|
||||
string formStart = "<form method=\"post\" name=\"" + name + "\" id=\"" + name + "\" action=\"" + action +
|
||||
"\"";
|
||||
if (isAjax)
|
||||
return
|
||||
formStart +
|
||||
"onsubmit=\"" + JSImplementation.AjaxFormOnSubmit() + "\">";
|
||||
|
||||
return formStart + ">";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a link tag.
|
||||
/// </summary>
|
||||
/// <param name="url">url to go to</param>
|
||||
/// <param name="title">link title (text that is displayed)</param>
|
||||
/// <param name="htmlAttributes">html attributes, name, value, name, value</param>
|
||||
/// <returns>html code</returns>
|
||||
/// <example>
|
||||
/// WebHelper.Link("/user/show/1", "Show user", "id", "showUser", "onclick", "return confirm('Are you shure?');");
|
||||
/// </example>
|
||||
public static string Link(string url, string title, params string[] htmlAttributes)
|
||||
{
|
||||
return BuildLink(url, title, htmlAttributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a link
|
||||
/// </summary>
|
||||
/// <param name="url">url to go to.</param>
|
||||
/// <param name="title">title of link (displayed text)</param>
|
||||
/// <param name="htmlAttributes">extra html attributes.</param>
|
||||
/// <returns>a complete link</returns>
|
||||
public static string BuildLink(string url, string title, params string[] htmlAttributes)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("<a href=\"");
|
||||
sb.Append(url);
|
||||
sb.Append("\"");
|
||||
GenerateHtmlAttributes(sb, htmlAttributes);
|
||||
sb.Append(">");
|
||||
sb.Append(title);
|
||||
sb.Append("</a>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a link
|
||||
/// </summary>
|
||||
/// <param name="url">url to go to.</param>
|
||||
/// <param name="title">title of link (displayed text)</param>
|
||||
/// <param name="htmlAttributes">extra html attributes.</param>
|
||||
/// <returns>a complete link</returns>
|
||||
/// <param name="options">more options</param>
|
||||
internal static string BuildLink(string url, string title, string[] htmlAttributes, params string[] options)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("<a href=\"");
|
||||
sb.Append(url);
|
||||
sb.Append("\"");
|
||||
GenerateHtmlAttributes(sb, htmlAttributes, options);
|
||||
sb.Append(">");
|
||||
sb.Append(title);
|
||||
sb.Append("</a>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete
|
||||
/// </summary>
|
||||
/// <param name="name">Obsolete</param>
|
||||
/// <param name="collection">Obsolete</param>
|
||||
/// <param name="getIdTitle">Obsolete</param>
|
||||
/// <param name="selectedValue">Obsolete</param>
|
||||
/// <param name="firstEmpty">Obsolete</param>
|
||||
/// <returns>Obsolete</returns>
|
||||
[Obsolete("Moved to FormHelper")]
|
||||
public static string Select(string name, ICollection collection, GetIdTitle getIdTitle, object selectedValue, bool firstEmpty)
|
||||
{
|
||||
return FormHelper.Select(name, name, collection, getIdTitle, selectedValue, firstEmpty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete
|
||||
/// </summary>
|
||||
/// <param name="name">Obsolete</param>
|
||||
/// <param name="id">Obsolete</param>
|
||||
/// <param name="collection">Obsolete</param>
|
||||
/// <param name="getIdTitle">Obsolete</param>
|
||||
/// <param name="selectedValue">Obsolete</param>
|
||||
/// <param name="firstEmpty">Obsolete</param>
|
||||
/// <returns>Obsolete</returns>
|
||||
[Obsolete("Moved to FormHelper")]
|
||||
public static string Select(string name, string id, ICollection collection, GetIdTitle getIdTitle, object selectedValue, bool firstEmpty)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("<select name=\"" + name + "\" id=\"" + id + "\">");
|
||||
if (firstEmpty)
|
||||
sb.AppendLine("<option value=\"\"> </option>");
|
||||
|
||||
|
||||
foreach (object o in collection)
|
||||
{
|
||||
object value;
|
||||
string title;
|
||||
getIdTitle(o, out value, out title);
|
||||
sb.Append("<option value=\"");
|
||||
if (value != null)
|
||||
sb.Append(value);
|
||||
sb.Append("\"");
|
||||
|
||||
if (value != null && value.Equals(selectedValue))
|
||||
sb.Append(" selected=\"selected\"");
|
||||
|
||||
sb.Append(">");
|
||||
|
||||
if (title == null)
|
||||
sb.AppendLine(" </option>");
|
||||
else
|
||||
{
|
||||
sb.Append(value);
|
||||
sb.AppendLine("</option>");
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine("</select>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render errors into a UL with class "errors"
|
||||
/// </summary>
|
||||
/// <param name="className">class used by UL-tag.</param>
|
||||
/// <param name="theList">items to list</param>
|
||||
/// <returns>an unordered html list.</returns>
|
||||
public static string List(IEnumerable<object> theList, string className)
|
||||
{
|
||||
if (theList.GetEnumerator().Current == null)
|
||||
return string.Empty;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("<ul class=\"");
|
||||
sb.Append(className);
|
||||
sb.AppendLine("\">");
|
||||
foreach (string error in theList)
|
||||
{
|
||||
sb.Append("<li>");
|
||||
sb.Append(error);
|
||||
sb.AppendLine("</li>");
|
||||
}
|
||||
|
||||
sb.AppendLine("</ul>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Render errors into a UL with class "errors"
|
||||
/// </summary>
|
||||
/// <param name="className">class used by UL-tag.</param>
|
||||
/// <param name="theList">items to list</param>
|
||||
/// <returns>an unordered html list.</returns>
|
||||
public static string List(NameValueCollection theList, string className)
|
||||
{
|
||||
if (theList.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("<ul class=\"");
|
||||
sb.Append(className);
|
||||
sb.AppendLine("\">");
|
||||
foreach (string key in theList.AllKeys)
|
||||
{
|
||||
sb.Append("<li>");
|
||||
sb.Append(theList[key]);
|
||||
sb.AppendLine("</li>");
|
||||
}
|
||||
|
||||
sb.AppendLine("</ul>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Render errors into a UL with class "errors"
|
||||
/// </summary>
|
||||
/// <param name="errors"></param>
|
||||
/// <returns></returns>
|
||||
public static string Errors(NameValueCollection errors)
|
||||
{
|
||||
return List(errors, "error");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a list with html attributes.
|
||||
/// </summary>
|
||||
/// <param name="sb">StringBuilder that the options should be added to.</param>
|
||||
/// <param name="firstOptions">attributes set by user.</param>
|
||||
/// <param name="secondOptions">attributes set by any of the helper classes.</param>
|
||||
internal static void GenerateHtmlAttributes(StringBuilder sb, string[] firstOptions, string[] secondOptions)
|
||||
{
|
||||
if (firstOptions.Length % 2 == 1)
|
||||
throw new ArgumentException("Options must be in pairs [key,value]", "firstOptions");
|
||||
if (secondOptions.Length % 2 == 1)
|
||||
throw new ArgumentException("Options must be in pairs [key,value]", "secondOptions");
|
||||
|
||||
Dictionary<string, List<string>> options = new Dictionary<string, List<string>>();
|
||||
for (int i = 0; i < firstOptions.Length; i += 2)
|
||||
{
|
||||
if(!options.ContainsKey(firstOptions[i]))
|
||||
options.Add(firstOptions[i], new List<string>());
|
||||
options[firstOptions[i]].Add(firstOptions[i + 1]);
|
||||
}
|
||||
for (int i = 0; i < secondOptions.Length; i += 2)
|
||||
{
|
||||
if (!options.ContainsKey(secondOptions[i]))
|
||||
options.Add(secondOptions[i], new List<string>());
|
||||
options[secondOptions[i]].Add(secondOptions[i + 1]);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, List<string>> option in options)
|
||||
{
|
||||
sb.Append(" ");
|
||||
sb.Append(option.Key);
|
||||
sb.Append("=\"");
|
||||
for (int i = 0; i < option.Value.Count; ++i )
|
||||
sb.Append(i != option.Value.Count - 1 ? option.Value[i] + " " : option.Value[i]);
|
||||
|
||||
sb.Append("\" ");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a list with html attributes.
|
||||
/// </summary>
|
||||
/// <param name="sb">StringBuilder that the options should be added to.</param>
|
||||
/// <param name="options"></param>
|
||||
internal static void GenerateHtmlAttributes(StringBuilder sb, string[] options)
|
||||
{
|
||||
for (int i = 0; i < options.Length; i += 2)
|
||||
{
|
||||
if (string.IsNullOrEmpty(options[i]) || options[i][options[i].Length - 1] == ':')
|
||||
continue;
|
||||
|
||||
sb.Append(" ");
|
||||
sb.Append(options[i]);
|
||||
sb.Append("=\"");
|
||||
sb.Append(options[i + 1]);
|
||||
sb.Append("\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Purpose of this class is to create a javascript toolkit independent javascript helper.
|
||||
/// </summary>
|
||||
public abstract class JavascriptHelperImplementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a list with JS options.
|
||||
/// </summary>
|
||||
/// <param name="sb">StringBuilder that the options should be added to.</param>
|
||||
/// <param name="options">the javascript options. name, value pairs. each string value should be escaped by YOU!</param>
|
||||
/// <param name="startWithComma">true if we should start with a comma.</param>
|
||||
internal virtual void GenerateOptions(StringBuilder sb, string[] options, bool startWithComma)
|
||||
{
|
||||
if (startWithComma)
|
||||
sb.Append(", ");
|
||||
|
||||
long len = sb.Length;
|
||||
for (int i = 0; i < options.Length; i += 2)
|
||||
{
|
||||
if (string.IsNullOrEmpty(options[i]) || options[i][options[i].Length - 1] != ':')
|
||||
continue;
|
||||
|
||||
sb.Append(options[i]);
|
||||
sb.Append(" ");
|
||||
sb.Append(options[i + 1]);
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
// remove the last comma.
|
||||
if (sb.Length != len || startWithComma)
|
||||
sb.Length -= 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes any javascript parameters from an array of parameters
|
||||
/// </summary>
|
||||
/// <param name="options">The array of parameters to remove javascript params from</param>
|
||||
/// <returns>An array of html parameters</returns>
|
||||
internal virtual string[] RemoveJavascriptOptions(string[] options)
|
||||
{
|
||||
if(options == null)
|
||||
throw new ArgumentNullException("options");
|
||||
if(options.Length%2 == 1)
|
||||
throw new ArgumentException("Options must be in pairs [key,value]", "options");
|
||||
|
||||
List<string> saveParams = new List<string>();
|
||||
for(int i = 0; i < options.Length; i += 2)
|
||||
{
|
||||
if(!options[i].TrimEnd().EndsWith(":"))
|
||||
{
|
||||
saveParams.Add(options[i]);
|
||||
saveParams.Add(options[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
return saveParams.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// javascript action that should be added to the "onsubmit" event in the form tag.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>All javascript option names should end with colon.</remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// JSHelper.AjaxRequest("/user/show/1", "onsuccess:", "$('userInfo').update(result);");
|
||||
/// </code>
|
||||
/// </example>
|
||||
public abstract string AjaxFormOnSubmit(params string[] options);
|
||||
|
||||
/// <summary>
|
||||
/// Requests a url through ajax
|
||||
/// </summary>
|
||||
/// <param name="url">url to fetch</param>
|
||||
/// <param name="options">optional options in format "key, value, key, value", used in JS request object.</param>
|
||||
/// <returns>a link tag</returns>
|
||||
/// <remarks>All javascript option names should end with colon.</remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// JSHelper.AjaxRequest("/user/show/1", "onsuccess:", "$('userInfo').update(result);");
|
||||
/// </code>
|
||||
/// </example>
|
||||
public abstract string AjaxRequest(string url, params string[] options);
|
||||
|
||||
/// <summary>
|
||||
/// Ajax requests that updates an element with
|
||||
/// the fetched content
|
||||
/// </summary>
|
||||
/// <param name="url">Url to fetch content from</param>
|
||||
/// <param name="targetId">element to update</param>
|
||||
/// <param name="options">optional options in format "key, value, key, value", used in JS updater object.</param>
|
||||
/// <returns>A link tag.</returns>
|
||||
/// <remarks>All javascript option names should end with colon.</remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// JSHelper.AjaxUpdater("/user/show/1", "userInfo", "onsuccess:", "alert('Successful!');");
|
||||
/// </code>
|
||||
/// </example>
|
||||
public abstract string AjaxUpdater(string url, string targetId, params string[] options);
|
||||
|
||||
/// <summary>
|
||||
/// A link that pop ups a Dialog (overlay div)
|
||||
/// </summary>
|
||||
/// <param name="url">url to contents of dialog</param>
|
||||
/// <param name="title">link title</param>
|
||||
/// <returns>A "a"-tag that popups a dialog when clicked</returns>
|
||||
/// <param name="htmlAttributes">name/value of html attributes</param>
|
||||
/// <example>
|
||||
/// WebHelper.DialogLink("/user/show/1", "show user", "onmouseover", "alert('booh!');");
|
||||
/// </example>
|
||||
public abstract string DialogLink(string url, string title, params string[] htmlAttributes);
|
||||
|
||||
/// <summary>
|
||||
/// Close a javascript dialog window/div.
|
||||
/// </summary>
|
||||
/// <returns>javascript for closing a dialog.</returns>
|
||||
/// <see cref="DialogLink"/>
|
||||
public abstract string CloseDialog();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new modal dialog window
|
||||
/// </summary>
|
||||
/// <param name="url">url to open in window.</param>
|
||||
/// <param name="title">window title (may not be supported by all js implementations)</param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public abstract string CreateDialog(string url, string title, params string[] options);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace HttpServer.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to make XML handling easier
|
||||
/// </summary>
|
||||
public static class XmlHelper
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Serializes object to XML.
|
||||
/// </summary>
|
||||
/// <param name="value">object to serialize.</param>
|
||||
/// <returns>XML</returns>
|
||||
/// <remarks>
|
||||
/// Removes name spaces and adds indentation
|
||||
/// </remarks>
|
||||
public static string Serialize(object value)
|
||||
{
|
||||
Check.Require(value, "value");
|
||||
|
||||
//These to lines are nessacary to get rid of the default namespaces.
|
||||
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
|
||||
ns.Add(string.Empty, string.Empty);
|
||||
|
||||
// removing XML declaration, the default is false
|
||||
XmlWriterSettings xmlSettings = new XmlWriterSettings();
|
||||
xmlSettings.Indent = true;
|
||||
xmlSettings.IndentChars = "\t";
|
||||
xmlSettings.OmitXmlDeclaration = true;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
using (XmlWriter writer = XmlWriter.Create(sb, xmlSettings))
|
||||
{
|
||||
XmlSerializer serializer = new XmlSerializer(value.GetType());
|
||||
serializer.Serialize(writer, value, ns);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an object from a XML string
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of object</typeparam>
|
||||
/// <param name="xml">XML string</param>
|
||||
/// <returns>object</returns>
|
||||
public static T Deserialize<T>(string xml)
|
||||
{
|
||||
Check.NotEmpty(xml, "xml");
|
||||
|
||||
XmlSerializer serializer = new XmlSerializer(typeof(T));
|
||||
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
|
||||
{
|
||||
return (T)serializer.Deserialize(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,808 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.Parser;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains a connection to a browser/client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Remember to <see cref="Start"/> after you have hooked the <see cref="RequestReceived"/> event.
|
||||
/// </remarks>
|
||||
/// TODO: Maybe this class should be broken up into HttpClientChannel and HttpClientContext?
|
||||
public class HttpClientContext : IHttpClientContext, IDisposable
|
||||
{
|
||||
const int MAXREQUESTS = 20;
|
||||
const int MAXKEEPALIVE = 60000;
|
||||
|
||||
static private int basecontextID;
|
||||
|
||||
private readonly byte[] m_ReceiveBuffer;
|
||||
private int m_ReceiveBytesLeft;
|
||||
private ILogWriter _log;
|
||||
private readonly IHttpRequestParser m_parser;
|
||||
private readonly int m_bufferSize;
|
||||
private HashSet<uint> requestsInServiceIDs;
|
||||
private Socket m_sock;
|
||||
|
||||
public bool Available = true;
|
||||
public bool StreamPassedOff = false;
|
||||
|
||||
public int MonitorStartMS = 0;
|
||||
public int MonitorKeepaliveMS = 0;
|
||||
public bool TriggerKeepalive = false;
|
||||
public int TimeoutFirstLine = 70000; // 70 seconds
|
||||
public int TimeoutRequestReceived = 180000; // 180 seconds
|
||||
|
||||
// The difference between this and request received is on POST more time is needed before we get the full request.
|
||||
public int TimeoutFullRequestProcessed = 600000; // 10 minutes
|
||||
public int m_TimeoutKeepAlive = MAXKEEPALIVE; // 400 seconds before keepalive timeout
|
||||
// public int TimeoutKeepAlive = 120000; // 400 seconds before keepalive timeout
|
||||
|
||||
public int m_maxRequests = MAXREQUESTS;
|
||||
|
||||
public bool FirstRequestLineReceived;
|
||||
public bool FullRequestReceived;
|
||||
public bool FullRequestProcessed;
|
||||
|
||||
private bool isSendingResponse = false;
|
||||
|
||||
private HttpRequest m_currentRequest;
|
||||
private HttpResponse m_currentResponse;
|
||||
|
||||
public int contextID { get; private set; }
|
||||
public int TimeoutKeepAlive
|
||||
{
|
||||
get { return m_TimeoutKeepAlive; }
|
||||
set
|
||||
{
|
||||
m_TimeoutKeepAlive = (value > MAXKEEPALIVE) ? MAXKEEPALIVE : value;
|
||||
}
|
||||
}
|
||||
|
||||
public int MAXRequests
|
||||
{
|
||||
get { return m_maxRequests; }
|
||||
set
|
||||
{
|
||||
m_maxRequests = value > MAXREQUESTS ? MAXREQUESTS : value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSending()
|
||||
{
|
||||
return isSendingResponse;
|
||||
}
|
||||
|
||||
public bool StopMonitoring;
|
||||
|
||||
/// <summary>
|
||||
/// Context have been started (a new client have connected)
|
||||
/// </summary>
|
||||
public event EventHandler Started;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpClientContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="secured">true if the connection is secured (SSL/TLS)</param>
|
||||
/// <param name="remoteEndPoint">client that connected.</param>
|
||||
/// <param name="stream">Stream used for communication</param>
|
||||
/// <param name="parserFactory">Used to create a <see cref="IHttpRequestParser"/>.</param>
|
||||
/// <param name="bufferSize">Size of buffer to use when reading data. Must be at least 4096 bytes.</param>
|
||||
/// <exception cref="SocketException">If <see cref="Socket.BeginReceive(byte[],int,int,SocketFlags,AsyncCallback,object)"/> fails</exception>
|
||||
/// <exception cref="ArgumentException">Stream must be writable and readable.</exception>
|
||||
public HttpClientContext(bool secured, IPEndPoint remoteEndPoint,
|
||||
Stream stream, IRequestParserFactory parserFactory, Socket sock)
|
||||
{
|
||||
Check.Require(remoteEndPoint, "remoteEndPoint");
|
||||
Check.NotEmpty(remoteEndPoint.Address.ToString(), "remoteEndPoint.Address");
|
||||
Check.Require(stream, "stream");
|
||||
Check.Require(parserFactory, "parser");
|
||||
Check.Require(sock, "socket");
|
||||
|
||||
if (!stream.CanWrite || !stream.CanRead)
|
||||
throw new ArgumentException("Stream must be writable and readable.");
|
||||
|
||||
RemoteAddress = remoteEndPoint.Address.ToString();
|
||||
RemotePort = remoteEndPoint.Port.ToString();
|
||||
_log = NullLogWriter.Instance;
|
||||
m_parser = parserFactory.CreateParser(_log);
|
||||
m_parser.RequestCompleted += OnRequestCompleted;
|
||||
m_parser.RequestLineReceived += OnRequestLine;
|
||||
m_parser.HeaderReceived += OnHeaderReceived;
|
||||
m_parser.BodyBytesReceived += OnBodyBytesReceived;
|
||||
m_currentRequest = new HttpRequest(this);
|
||||
IsSecured = secured;
|
||||
_stream = stream;
|
||||
m_sock = sock;
|
||||
|
||||
m_bufferSize = 8196;
|
||||
m_ReceiveBuffer = new byte[m_bufferSize];
|
||||
requestsInServiceIDs = new HashSet<uint>();
|
||||
|
||||
SSLCommonName = "";
|
||||
if (secured)
|
||||
{
|
||||
SslStream _ssl = (SslStream)_stream;
|
||||
X509Certificate _cert1 = _ssl.RemoteCertificate;
|
||||
if (_cert1 != null)
|
||||
{
|
||||
X509Certificate2 _cert2 = new X509Certificate2(_cert1);
|
||||
if (_cert2 != null)
|
||||
SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false);
|
||||
}
|
||||
}
|
||||
|
||||
++basecontextID;
|
||||
if (basecontextID <= 0)
|
||||
basecontextID = 1;
|
||||
|
||||
contextID = basecontextID;
|
||||
}
|
||||
|
||||
public bool CanSend()
|
||||
{
|
||||
if (contextID < 0)
|
||||
return false;
|
||||
|
||||
if (Stream == null || m_sock == null || !m_sock.Connected)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process incoming body bytes.
|
||||
/// </summary>
|
||||
/// <param name="sender"><see cref="IHttpRequestParser"/></param>
|
||||
/// <param name="e">Bytes</param>
|
||||
protected virtual void OnBodyBytesReceived(object sender, BodyEventArgs e)
|
||||
{
|
||||
m_currentRequest.AddToBody(e.Buffer, e.Offset, e.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void OnHeaderReceived(object sender, HeaderEventArgs e)
|
||||
{
|
||||
if (string.Compare(e.Name, "expect", true) == 0 && e.Value.Contains("100-continue"))
|
||||
{
|
||||
lock (requestsInServiceIDs)
|
||||
{
|
||||
if (requestsInServiceIDs.Count == 0)
|
||||
Respond("HTTP/1.1", HttpStatusCode.Continue, "Please continue.");
|
||||
}
|
||||
}
|
||||
m_currentRequest.AddHeader(e.Name, e.Value);
|
||||
}
|
||||
|
||||
private void OnRequestLine(object sender, RequestLineEventArgs e)
|
||||
{
|
||||
m_currentRequest.Method = e.HttpMethod;
|
||||
m_currentRequest.HttpVersion = e.HttpVersion;
|
||||
m_currentRequest.UriPath = e.UriPath;
|
||||
m_currentRequest.AddHeader("remote_addr", RemoteAddress);
|
||||
m_currentRequest.AddHeader("remote_port", RemotePort);
|
||||
FirstRequestLineReceived = true;
|
||||
TriggerKeepalive = false;
|
||||
MonitorKeepaliveMS = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start reading content.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Make sure to call base.Start() if you override this method.
|
||||
/// </remarks>
|
||||
public virtual void Start()
|
||||
{
|
||||
ReceiveLoop();
|
||||
Started?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean up context.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Make sure to call base.Cleanup() if you override the method.
|
||||
/// </remarks>
|
||||
public virtual void Cleanup()
|
||||
{
|
||||
if (StreamPassedOff)
|
||||
return;
|
||||
|
||||
if (Stream != null)
|
||||
{
|
||||
Stream.Close();
|
||||
Stream = null;
|
||||
m_sock = null;
|
||||
}
|
||||
|
||||
m_currentRequest?.Clear();
|
||||
m_currentRequest = null;
|
||||
m_currentResponse?.Clear();
|
||||
m_currentResponse = null;
|
||||
requestsInServiceIDs.Clear();
|
||||
|
||||
FirstRequestLineReceived = false;
|
||||
FullRequestReceived = false;
|
||||
FullRequestProcessed = false;
|
||||
MonitorStartMS = 0;
|
||||
StopMonitoring = true;
|
||||
MonitorKeepaliveMS = 0;
|
||||
TriggerKeepalive = false;
|
||||
|
||||
isSendingResponse = false;
|
||||
|
||||
m_ReceiveBytesLeft = 0;
|
||||
|
||||
contextID = -100;
|
||||
m_parser.Clear();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Using SSL or other encryption method.
|
||||
/// </summary>
|
||||
[Obsolete("Use IsSecured instead.")]
|
||||
public bool Secured
|
||||
{
|
||||
get { return IsSecured; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Using SSL or other encryption method.
|
||||
/// </summary>
|
||||
public bool IsSecured { get; internal set; }
|
||||
|
||||
|
||||
// returns the SSL commonName of remote Certificate
|
||||
public string SSLCommonName { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specify which logger to use.
|
||||
/// </summary>
|
||||
public ILogWriter LogWriter
|
||||
{
|
||||
get { return _log; }
|
||||
set
|
||||
{
|
||||
_log = value ?? NullLogWriter.Instance;
|
||||
m_parser.LogWriter = _log;
|
||||
}
|
||||
}
|
||||
|
||||
private Stream _stream;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the network stream.
|
||||
/// </summary>
|
||||
internal Stream Stream
|
||||
{
|
||||
get { return _stream; }
|
||||
set { _stream = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets IP address that the client connected from.
|
||||
/// </summary>
|
||||
internal string RemoteAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets port that the client connected from.
|
||||
/// </summary>
|
||||
internal string RemotePort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from client
|
||||
/// </summary>
|
||||
/// <param name="error">error to report in the <see cref="Disconnected"/> event.</param>
|
||||
public void Disconnect(SocketError error)
|
||||
{
|
||||
// disconnect may not throw any exceptions
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Stream != null)
|
||||
{
|
||||
if (error == SocketError.Success)
|
||||
Stream.Flush();
|
||||
Stream.Close();
|
||||
Stream = null;
|
||||
}
|
||||
m_sock = null;
|
||||
}
|
||||
catch { }
|
||||
|
||||
Disconnected(this, new DisconnectedEventArgs(error));
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Error, "Disconnect threw an exception: " + err);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReceive(IAsyncResult ar)
|
||||
{
|
||||
try
|
||||
{
|
||||
int bytesRead = 0;
|
||||
if (Stream == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
bytesRead = Stream.EndRead(ar);
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
Disconnect(SocketError.ConnectionReset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
Disconnect(SocketError.ConnectionReset);
|
||||
return;
|
||||
}
|
||||
|
||||
m_ReceiveBytesLeft += bytesRead;
|
||||
if (m_ReceiveBytesLeft > m_ReceiveBuffer.Length)
|
||||
{
|
||||
throw new BadRequestException("HTTP header Too large: " + m_ReceiveBytesLeft);
|
||||
}
|
||||
|
||||
int offset = m_parser.Parse(m_ReceiveBuffer, 0, m_ReceiveBytesLeft);
|
||||
if (Stream == null)
|
||||
return; // "Connection: Close" in effect.
|
||||
|
||||
// try again to see if we can parse another message (check parser to see if it is looking for a new message)
|
||||
int nextOffset;
|
||||
int nextBytesleft = m_ReceiveBytesLeft - offset;
|
||||
|
||||
while (offset != 0 && nextBytesleft > 0)
|
||||
{
|
||||
nextOffset = m_parser.Parse(m_ReceiveBuffer, offset, nextBytesleft);
|
||||
|
||||
if (Stream == null)
|
||||
return; // "Connection: Close" in effect.
|
||||
|
||||
if (nextOffset == 0)
|
||||
break;
|
||||
|
||||
offset = nextOffset;
|
||||
nextBytesleft = m_ReceiveBytesLeft - offset;
|
||||
}
|
||||
|
||||
// copy unused bytes to the beginning of the array
|
||||
if (offset > 0 && m_ReceiveBytesLeft > offset)
|
||||
Buffer.BlockCopy(m_ReceiveBuffer, offset, m_ReceiveBuffer, 0, m_ReceiveBytesLeft - offset);
|
||||
|
||||
m_ReceiveBytesLeft -= offset;
|
||||
if (Stream != null && Stream.CanRead)
|
||||
{
|
||||
if (!StreamPassedOff)
|
||||
Stream.BeginRead(m_ReceiveBuffer, m_ReceiveBytesLeft, m_ReceiveBuffer.Length - m_ReceiveBytesLeft, OnReceive, null);
|
||||
else
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "Could not read any more from the socket.");
|
||||
Disconnect(SocketError.Success);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (BadRequestException err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Warning, "Bad request, responding with it. Error: " + err);
|
||||
try
|
||||
{
|
||||
Respond("HTTP/1.0", HttpStatusCode.BadRequest, err.Message);
|
||||
}
|
||||
catch (Exception err2)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Fatal, "Failed to reply to a bad request. " + err2);
|
||||
}
|
||||
Disconnect(SocketError.NoRecovery);
|
||||
}
|
||||
catch (IOException err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message);
|
||||
if (err.InnerException is SocketException)
|
||||
Disconnect((SocketError)((SocketException)err.InnerException).ErrorCode);
|
||||
else
|
||||
Disconnect(SocketError.ConnectionReset);
|
||||
}
|
||||
catch (ObjectDisposedException err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : " + err.Message);
|
||||
Disconnect(SocketError.NotSocket);
|
||||
}
|
||||
catch (NullReferenceException err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : NullRef: " + err.Message);
|
||||
Disconnect(SocketError.NoRecovery);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message);
|
||||
Disconnect(SocketError.NoRecovery);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ReceiveLoop()
|
||||
{
|
||||
m_ReceiveBytesLeft = 0;
|
||||
try
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
if (_stream == null || !_stream.CanRead)
|
||||
return;
|
||||
|
||||
int bytesRead = await _stream.ReadAsync(m_ReceiveBuffer, m_ReceiveBytesLeft, m_ReceiveBuffer.Length - m_ReceiveBytesLeft).ConfigureAwait(false);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
Disconnect(SocketError.ConnectionReset);
|
||||
return;
|
||||
}
|
||||
|
||||
m_ReceiveBytesLeft += bytesRead;
|
||||
if (m_ReceiveBytesLeft > m_ReceiveBuffer.Length)
|
||||
throw new BadRequestException("HTTP header Too large: " + m_ReceiveBytesLeft);
|
||||
|
||||
int offset = m_parser.Parse(m_ReceiveBuffer, 0, m_ReceiveBytesLeft);
|
||||
if (Stream == null)
|
||||
return; // "Connection: Close" in effect.
|
||||
|
||||
// try again to see if we can parse another message (check parser to see if it is looking for a new message)
|
||||
int nextOffset;
|
||||
int nextBytesleft = m_ReceiveBytesLeft - offset;
|
||||
|
||||
while (offset != 0 && nextBytesleft > 0)
|
||||
{
|
||||
nextOffset = m_parser.Parse(m_ReceiveBuffer, offset, nextBytesleft);
|
||||
|
||||
if (Stream == null)
|
||||
return; // "Connection: Close" in effect.
|
||||
|
||||
if (nextOffset == 0)
|
||||
break;
|
||||
|
||||
offset = nextOffset;
|
||||
nextBytesleft = m_ReceiveBytesLeft - offset;
|
||||
}
|
||||
|
||||
// copy unused bytes to the beginning of the array
|
||||
if (offset > 0 && m_ReceiveBytesLeft > offset)
|
||||
Buffer.BlockCopy(m_ReceiveBuffer, offset, m_ReceiveBuffer, 0, m_ReceiveBytesLeft - offset);
|
||||
|
||||
m_ReceiveBytesLeft -= offset;
|
||||
if (StreamPassedOff)
|
||||
return; //?
|
||||
}
|
||||
}
|
||||
catch (BadRequestException err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Warning, "Bad request, responding with it. Error: " + err);
|
||||
try
|
||||
{
|
||||
Respond("HTTP/1.0", HttpStatusCode.BadRequest, err.Message);
|
||||
}
|
||||
catch (Exception err2)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Fatal, "Failed to reply to a bad request. " + err2);
|
||||
}
|
||||
Disconnect(SocketError.NoRecovery);
|
||||
}
|
||||
catch (IOException err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message);
|
||||
if (err.InnerException is SocketException)
|
||||
Disconnect((SocketError)((SocketException)err.InnerException).ErrorCode);
|
||||
else
|
||||
Disconnect(SocketError.ConnectionReset);
|
||||
}
|
||||
catch (ObjectDisposedException err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : " + err.Message);
|
||||
Disconnect(SocketError.NotSocket);
|
||||
}
|
||||
catch (NullReferenceException err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : NullRef: " + err.Message);
|
||||
Disconnect(SocketError.NoRecovery);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message);
|
||||
Disconnect(SocketError.NoRecovery);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRequestCompleted(object source, EventArgs args)
|
||||
{
|
||||
TriggerKeepalive = false;
|
||||
MonitorKeepaliveMS = 0;
|
||||
|
||||
// load cookies if they exist
|
||||
RequestCookies cookies = m_currentRequest.Headers["cookie"] != null
|
||||
? new RequestCookies(m_currentRequest.Headers["cookie"])
|
||||
: new RequestCookies(String.Empty);
|
||||
m_currentRequest.SetCookies(cookies);
|
||||
|
||||
m_currentRequest.Body.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
FullRequestReceived = true;
|
||||
|
||||
int nreqs;
|
||||
lock (requestsInServiceIDs)
|
||||
{
|
||||
nreqs = requestsInServiceIDs.Count;
|
||||
requestsInServiceIDs.Add(m_currentRequest.ID);
|
||||
if (m_maxRequests > 0)
|
||||
m_maxRequests--;
|
||||
}
|
||||
|
||||
// for now pipeline requests need to be serialized by opensim
|
||||
RequestReceived(this, new RequestEventArgs(m_currentRequest));
|
||||
|
||||
m_currentRequest = new HttpRequest(this);
|
||||
|
||||
int nreqsnow;
|
||||
lock (requestsInServiceIDs)
|
||||
{
|
||||
nreqsnow = requestsInServiceIDs.Count;
|
||||
}
|
||||
if (nreqs != nreqsnow)
|
||||
{
|
||||
// request was not done by us
|
||||
}
|
||||
}
|
||||
|
||||
public void ReqResponseAboutToSend(uint requestID)
|
||||
{
|
||||
isSendingResponse = true;
|
||||
}
|
||||
|
||||
public void StartSendResponse(HttpResponse response)
|
||||
{
|
||||
isSendingResponse = true;
|
||||
m_currentResponse = response;
|
||||
ContextTimeoutManager.EnqueueSend(this, response.Priority);
|
||||
}
|
||||
|
||||
public bool TrySendResponse(int bytesLimit)
|
||||
{
|
||||
if(m_currentResponse == null)
|
||||
return false;
|
||||
if (m_currentResponse.Sent)
|
||||
return false;
|
||||
|
||||
if(!CanSend())
|
||||
return false;
|
||||
|
||||
m_currentResponse?.SendNextAsync(bytesLimit);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ContinueSendResponse()
|
||||
{
|
||||
if(m_currentResponse == null)
|
||||
return;
|
||||
ContextTimeoutManager.EnqueueSend(this, m_currentResponse.Priority);
|
||||
}
|
||||
|
||||
public void ReqResponseSent(uint requestID, ConnectionType ctype)
|
||||
{
|
||||
isSendingResponse = false;
|
||||
m_currentResponse?.Clear();
|
||||
m_currentResponse = null;
|
||||
|
||||
bool doclose = ctype == ConnectionType.Close;
|
||||
lock (requestsInServiceIDs)
|
||||
{
|
||||
requestsInServiceIDs.Remove(requestID);
|
||||
// doclose = doclose && requestsInServiceIDs.Count == 0;
|
||||
if (requestsInServiceIDs.Count > 1)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (doclose)
|
||||
Disconnect(SocketError.Success);
|
||||
else
|
||||
{
|
||||
lock (requestsInServiceIDs)
|
||||
{
|
||||
if (requestsInServiceIDs.Count == 0)
|
||||
TriggerKeepalive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a response.
|
||||
/// </summary>
|
||||
/// <param name="httpVersion">Either <see cref="HttpHelper.HTTP10"/> or <see cref="HttpHelper.HTTP11"/></param>
|
||||
/// <param name="statusCode">HTTP status code</param>
|
||||
/// <param name="reason">reason for the status code.</param>
|
||||
/// <param name="body">HTML body contents, can be null or empty.</param>
|
||||
/// <param name="contentType">A content type to return the body as, i.e. 'text/html' or 'text/plain', defaults to 'text/html' if null or empty</param>
|
||||
/// <exception cref="ArgumentException">If <paramref name="httpVersion"/> is invalid.</exception>
|
||||
public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
contentType = "text/html";
|
||||
|
||||
if (string.IsNullOrEmpty(httpVersion) || !httpVersion.StartsWith("HTTP/1"))
|
||||
throw new ArgumentException("Invalid HTTP version");
|
||||
|
||||
if (string.IsNullOrEmpty(reason))
|
||||
reason = statusCode.ToString();
|
||||
|
||||
string response = string.IsNullOrEmpty(body)
|
||||
? httpVersion + " " + (int)statusCode + " " + reason + "\r\n\r\n"
|
||||
: string.Format("{0} {1} {2}\r\nContent-Type: {5}\r\nContent-Length: {3}\r\n\r\n{4}",
|
||||
httpVersion, (int)statusCode, reason ?? statusCode.ToString(),
|
||||
body.Length, body, contentType);
|
||||
byte[] buffer = Encoding.ASCII.GetBytes(response);
|
||||
|
||||
Send(buffer);
|
||||
if (m_currentRequest.Connection == ConnectionType.Close)
|
||||
FullRequestProcessed = true;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a response.
|
||||
/// </summary>
|
||||
/// <param name="httpVersion">Either <see cref="HttpHelper.HTTP10"/> or <see cref="HttpHelper.HTTP11"/></param>
|
||||
/// <param name="statusCode">HTTP status code</param>
|
||||
/// <param name="reason">reason for the status code.</param>
|
||||
public void Respond(string httpVersion, HttpStatusCode statusCode, string reason)
|
||||
{
|
||||
Respond(httpVersion, statusCode, reason, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a response.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public void Respond(string body)
|
||||
{
|
||||
if (body == null)
|
||||
throw new ArgumentNullException("body");
|
||||
Respond("HTTP/1.1", HttpStatusCode.OK, HttpStatusCode.OK.ToString(), body, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// send a whole buffer
|
||||
/// </summary>
|
||||
/// <param name="buffer">buffer to send</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public bool Send(byte[] buffer)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
return Send(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data using the stream
|
||||
/// </summary>
|
||||
/// <param name="buffer">Contains data to send</param>
|
||||
/// <param name="offset">Start position in buffer</param>
|
||||
/// <param name="size">number of bytes to send</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
|
||||
private object sendLock = new object();
|
||||
|
||||
public bool Send(byte[] buffer, int offset, int size)
|
||||
{
|
||||
if (Stream == null || m_sock == null || !m_sock.Connected)
|
||||
return false;
|
||||
|
||||
if (offset + size > buffer.Length)
|
||||
throw new ArgumentOutOfRangeException("offset", offset, "offset + size is beyond end of buffer.");
|
||||
|
||||
bool ok = true;
|
||||
lock (sendLock) // can't have overlaps here
|
||||
{
|
||||
try
|
||||
{
|
||||
Stream.Write(buffer, offset, size);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (!ok && Stream != null)
|
||||
Disconnect(SocketError.NoRecovery);
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SendAsync(byte[] buffer, int offset, int size)
|
||||
{
|
||||
if (Stream == null || m_sock == null || !m_sock.Connected)
|
||||
return false;
|
||||
|
||||
if (offset + size > buffer.Length)
|
||||
throw new ArgumentOutOfRangeException("offset", offset, "offset + size is beyond end of buffer.");
|
||||
|
||||
bool ok = true;
|
||||
ContextTimeoutManager.ContextEnterActiveSend();
|
||||
try
|
||||
{
|
||||
await Stream.WriteAsync(buffer, offset, size).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ok = false;
|
||||
}
|
||||
|
||||
ContextTimeoutManager.ContextLeaveActiveSend();
|
||||
|
||||
if (!ok && Stream != null)
|
||||
Disconnect(SocketError.NoRecovery);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The context have been disconnected.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Event can be used to clean up a context, or to reuse it.
|
||||
/// </remarks>
|
||||
public event EventHandler<DisconnectedEventArgs> Disconnected = delegate { };
|
||||
/// <summary>
|
||||
/// A request have been received in the context.
|
||||
/// </summary>
|
||||
public event EventHandler<RequestEventArgs> RequestReceived = delegate { };
|
||||
|
||||
public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing()
|
||||
{
|
||||
StreamPassedOff = true;
|
||||
m_parser.RequestCompleted -= OnRequestCompleted;
|
||||
m_parser.RequestLineReceived -= OnRequestLine;
|
||||
m_parser.HeaderReceived -= OnHeaderReceived;
|
||||
m_parser.BodyBytesReceived -= OnBodyBytesReceived;
|
||||
m_parser.Clear();
|
||||
|
||||
return new HTTPNetworkContext() { Socket = m_sock, Stream = _stream as NetworkStream };
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (contextID >= 0)
|
||||
{
|
||||
StreamPassedOff = false;
|
||||
Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
~HttpClientContext()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to create and reuse contexts.
|
||||
/// </summary>
|
||||
public class HttpContextFactory : IHttpContextFactory
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, HttpClientContext> m_activeContexts = new ConcurrentDictionary<int, HttpClientContext>();
|
||||
private readonly IRequestParserFactory _factory;
|
||||
private readonly ILogWriter _logWriter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpContextFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer.</param>
|
||||
/// <param name="bufferSize">Amount of bytes to read from the incoming socket stream.</param>
|
||||
/// <param name="factory">Used to create a request parser.</param>
|
||||
public HttpContextFactory(ILogWriter writer, IRequestParserFactory factory)
|
||||
{
|
||||
_logWriter = writer;
|
||||
_factory = factory;
|
||||
ContextTimeoutManager.Start();
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// True if detailed trace logs should be written.
|
||||
///</summary>
|
||||
public bool UseTraceLogs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new context.
|
||||
/// </summary>
|
||||
/// <param name="isSecured">true if socket is running HTTPS.</param>
|
||||
/// <param name="endPoint">Client that connected</param>
|
||||
/// <param name="stream">Network/SSL stream.</param>
|
||||
/// <returns>A context.</returns>
|
||||
protected HttpClientContext CreateContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock)
|
||||
{
|
||||
HttpClientContext context;
|
||||
|
||||
context = CreateNewContext(isSecured, endPoint, stream, sock);
|
||||
context.Disconnected += OnFreeContext;
|
||||
context.RequestReceived += OnRequestReceived;
|
||||
|
||||
context.Stream = stream;
|
||||
context.IsSecured = isSecured;
|
||||
context.RemotePort = endPoint.Port.ToString();
|
||||
context.RemoteAddress = endPoint.Address.ToString();
|
||||
ContextTimeoutManager.StartMonitoringContext(context);
|
||||
m_activeContexts[context.contextID] = context;
|
||||
context.Start();
|
||||
return context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new context.
|
||||
/// </summary>
|
||||
/// <param name="isSecured">true if HTTPS is used.</param>
|
||||
/// <param name="endPoint">Remote client</param>
|
||||
/// <param name="stream">Network stream, <see cref="HttpClientContext"/></param>
|
||||
/// <returns>A new context (always).</returns>
|
||||
protected virtual HttpClientContext CreateNewContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock)
|
||||
{
|
||||
return new HttpClientContext(isSecured, endPoint, stream, _factory, sock);
|
||||
}
|
||||
|
||||
private void OnRequestReceived(object sender, RequestEventArgs e)
|
||||
{
|
||||
RequestReceived(sender, e);
|
||||
}
|
||||
|
||||
private void OnFreeContext(object sender, DisconnectedEventArgs e)
|
||||
{
|
||||
var imp = sender as HttpClientContext;
|
||||
if (imp == null || imp.contextID < 0)
|
||||
return;
|
||||
|
||||
m_activeContexts.TryRemove(imp.contextID, out HttpClientContext dummy);
|
||||
|
||||
imp.Close();
|
||||
}
|
||||
|
||||
|
||||
#region IHttpContextFactory Members
|
||||
|
||||
/// <summary>
|
||||
/// Create a secure <see cref="IHttpClientContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="socket">Client socket (accepted by the <see cref="HttpListener"/>).</param>
|
||||
/// <param name="certificate">HTTPS certificate to use.</param>
|
||||
/// <param name="protocol">Kind of HTTPS protocol. Usually TLS or SSL.</param>
|
||||
/// <returns>
|
||||
/// A created <see cref="IHttpClientContext"/>.
|
||||
/// </returns>
|
||||
public IHttpClientContext CreateSecureContext(Socket socket, X509Certificate certificate,
|
||||
SslProtocols protocol, RemoteCertificateValidationCallback _clientCallback = null)
|
||||
{
|
||||
socket.NoDelay = true;
|
||||
var networkStream = new NetworkStream(socket, true);
|
||||
var remoteEndPoint = (IPEndPoint)socket.RemoteEndPoint;
|
||||
|
||||
SslStream sslStream = null;
|
||||
try
|
||||
{
|
||||
if (_clientCallback == null)
|
||||
{
|
||||
sslStream = new SslStream(networkStream, false);
|
||||
sslStream.AuthenticateAsServer(certificate, false, protocol, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
sslStream = new SslStream(networkStream, false,
|
||||
new RemoteCertificateValidationCallback(_clientCallback));
|
||||
sslStream.AuthenticateAsServer(certificate, true, protocol, false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (UseTraceLogs)
|
||||
_logWriter.Write(this, LogPrio.Trace, e.Message);
|
||||
sslStream.Close();
|
||||
return null;
|
||||
}
|
||||
|
||||
return CreateContext(true, remoteEndPoint, sslStream, socket);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A request have been received from one of the contexts.
|
||||
/// </summary>
|
||||
public event EventHandler<RequestEventArgs> RequestReceived = delegate { };
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="IHttpClientContext"/> that handles a connected client.
|
||||
/// </summary>
|
||||
/// <param name="socket">Client socket (accepted by the <see cref="HttpListener"/>).</param>
|
||||
/// <returns>
|
||||
/// A creates <see cref="IHttpClientContext"/>.
|
||||
/// </returns>
|
||||
public IHttpClientContext CreateContext(Socket socket)
|
||||
{
|
||||
socket.NoDelay = true;
|
||||
var networkStream = new NetworkStream(socket, true);
|
||||
var remoteEndPoint = (IPEndPoint)socket.RemoteEndPoint;
|
||||
return CreateContext(false, remoteEndPoint, networkStream, socket);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Server is shutting down so shut down the factory
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
ContextTimeoutManager.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to create <see cref="IHttpClientContext"/>es.
|
||||
/// </summary>
|
||||
public interface IHttpContextFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="IHttpClientContext"/> that handles a connected client.
|
||||
/// </summary>
|
||||
/// <param name="socket">Client socket (accepted by the <see cref="HttpListener"/>).</param>
|
||||
/// <returns>A creates <see cref="IHttpClientContext"/>.</returns>
|
||||
IHttpClientContext CreateContext(Socket socket);
|
||||
|
||||
/// <summary>
|
||||
/// Create a secure <see cref="IHttpClientContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="socket">Client socket (accepted by the <see cref="HttpListener"/>).</param>
|
||||
/// <param name="certificate">HTTPS certificate to use.</param>
|
||||
/// <param name="protocol">Kind of HTTPS protocol. Usually TLS or SSL.</param>
|
||||
/// <returns>A created <see cref="IHttpClientContext"/>.</returns>
|
||||
IHttpClientContext CreateSecureContext(Socket socket, X509Certificate certificate,
|
||||
SslProtocols protocol, RemoteCertificateValidationCallback _clientCallback = null);
|
||||
|
||||
/// <summary>
|
||||
/// A request have been received from one of the contexts.
|
||||
/// </summary>
|
||||
event EventHandler<RequestEventArgs> RequestReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Server is shutting down so shut down the factory
|
||||
/// </summary>
|
||||
void Shutdown();
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Container class for posted files
|
||||
/// </summary>
|
||||
public class HttpFile : IDisposable
|
||||
{
|
||||
private string _name;
|
||||
private string _filename;
|
||||
private string _uploadFilename;
|
||||
private string _contentType;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a container for a posted file
|
||||
/// </summary>
|
||||
/// <param name="name">The identifier of the post field</param>
|
||||
/// <param name="filename">The file path</param>
|
||||
/// <param name="contentType">The content type of the file</param>
|
||||
/// <param name="uploadFilename">The name of the file uploaded</param>
|
||||
/// <exception cref="ArgumentNullException">If any parameter is null or empty</exception>
|
||||
public HttpFile(string name, string filename, string contentType, string uploadFilename)
|
||||
{
|
||||
if(string.IsNullOrEmpty(name))
|
||||
throw new ArgumentNullException("name");
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
throw new ArgumentNullException("filename");
|
||||
if(string.IsNullOrEmpty(contentType))
|
||||
throw new ArgumentNullException("contentType");
|
||||
if(string.IsNullOrEmpty(uploadFilename))
|
||||
throw new ArgumentNullException("uploadFilename");
|
||||
|
||||
_name = name;
|
||||
_filename = filename;
|
||||
_contentType = contentType;
|
||||
_uploadFilename = uploadFilename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a container for a posted file <see cref="HttpFile(string, string, string, string)"/>
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">If any parameter is null or empty</exception>
|
||||
public HttpFile(string name, string filename, string contentType) : this(name, filename, contentType, "Undefined")
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Destructor disposing the file</summary>
|
||||
~HttpFile()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name/id of the file
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertDisposed();
|
||||
return _name;
|
||||
}
|
||||
set { _name = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The full file path
|
||||
/// </summary>
|
||||
public string Filename
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertDisposed();
|
||||
return _filename;
|
||||
}
|
||||
set { _filename = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the uploaded file
|
||||
/// </summary>
|
||||
public string UploadFilename
|
||||
{
|
||||
get { return _uploadFilename; }
|
||||
set { _uploadFilename = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of file
|
||||
/// </summary>
|
||||
public string ContentType
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertDisposed();
|
||||
return _contentType;
|
||||
}
|
||||
set { _contentType = value; }
|
||||
}
|
||||
|
||||
private void AssertDisposed()
|
||||
{
|
||||
if(_disposed)
|
||||
throw new ObjectDisposedException("Object has been disposed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the temporary file
|
||||
/// </summary>
|
||||
/// <param name="disposing">True if manual dispose</param>
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (File.Exists(_filename))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(_filename);
|
||||
}
|
||||
catch(Exception err)
|
||||
{
|
||||
// todo, write logging code
|
||||
throw new Exception("Error deleting temporary file!", err);
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposing interface, cleans up managed resources (the temporary file) and suppresses finalization
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>Container for posted form data</summary>
|
||||
public class HttpForm : HttpInput
|
||||
{
|
||||
/// <summary>Instance to help mark a non-initialized form</summary>
|
||||
public static readonly HttpForm EmptyForm = new HttpForm("EmptyForm", true);
|
||||
|
||||
private readonly Dictionary<string, HttpFile> _files = new Dictionary<string, HttpFile>();
|
||||
|
||||
/// <summary>Initializes a form container with the specified name</summary>
|
||||
public HttpForm() : base("form")
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes a deep copy of the input
|
||||
/// </summary>
|
||||
/// <param name="input">The input to copy</param>
|
||||
public HttpForm(HttpInput input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
private HttpForm(string name, bool ignoreChanges) : base(name, ignoreChanges)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a file to the collection of posted files
|
||||
/// </summary>
|
||||
/// <param name="file">The file to add</param>
|
||||
/// <exception cref="ArgumentException">If the file is already added</exception>
|
||||
/// <exception cref="ArgumentNullException">If file is null</exception>
|
||||
/// <exception cref="InvalidOperationException">If the instance is HttpForm.EmptyForm which cannot be modified</exception>
|
||||
public void AddFile(HttpFile file)
|
||||
{
|
||||
if (_ignoreChanges)
|
||||
throw new InvalidOperationException("Cannot add files to instance HttpForm.EmptyForm.");
|
||||
if(file == null)
|
||||
throw new ArgumentNullException("file");
|
||||
|
||||
lock (_files)
|
||||
{
|
||||
if(_files.ContainsKey(file.Name))
|
||||
throw new ArgumentException("File named '" + file.Name + "' already exists!");
|
||||
|
||||
_files.Add(file.Name, file);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the form contains a specified file
|
||||
/// </summary>
|
||||
/// <param name="name">Field name of the file parameter</param>
|
||||
/// <returns>True if the file exists</returns>
|
||||
/// <exception cref="InvalidOperationException">If the instance is HttpForm.EmptyForm which cannot be modified</exception>
|
||||
public bool ContainsFile(string name)
|
||||
{
|
||||
if (_ignoreChanges)
|
||||
throw new InvalidOperationException("Cannot request information from instance HttpForm.EmptyForm.");
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ArgumentNullException("name");
|
||||
|
||||
bool res;
|
||||
lock (_files)
|
||||
{
|
||||
res = _files.ContainsKey(name);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a file held by by the form
|
||||
/// </summary>
|
||||
/// <param name="name">The identifier of the file</param>
|
||||
/// <returns>The requested file or null if the file was not found</returns>
|
||||
/// <exception cref="ArgumentNullException">If name is null or empty</exception>
|
||||
/// <exception cref="InvalidOperationException">If the instance is HttpForm.EmptyForm which cannot be modified</exception>
|
||||
public HttpFile GetFile(string name)
|
||||
{
|
||||
if (_ignoreChanges)
|
||||
throw new InvalidOperationException("Cannot retrieve files from instance HttpForm.EmptyForm.");
|
||||
if(string.IsNullOrEmpty(name))
|
||||
throw new ArgumentNullException("name");
|
||||
|
||||
HttpFile res;
|
||||
|
||||
lock (_files)
|
||||
{
|
||||
if(!_files.ContainsKey(name))
|
||||
return null;
|
||||
res = _files[name];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the number of files added to the <see cref="HttpForm"/>
|
||||
/// </summary>
|
||||
/// <returns>0 if no files are added</returns>
|
||||
public IList<HttpFile> Files
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_files)
|
||||
{
|
||||
if (_files.Count == 0)
|
||||
return EmptyFileCollection;
|
||||
|
||||
List<HttpFile> files = new List<HttpFile>();
|
||||
foreach (HttpFile file in _files.Values)
|
||||
files.Add(file);
|
||||
return files.AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly IList<HttpFile> EmptyFileCollection = new List<HttpFile>().AsReadOnly();
|
||||
|
||||
/// <summary>Disposes all held HttpFile's and resets values</summary>
|
||||
public override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
|
||||
lock (_files)
|
||||
{
|
||||
foreach (KeyValuePair<string, HttpFile> pair in _files)
|
||||
pair.Value.Dispose();
|
||||
|
||||
_files.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic helper functions for HTTP
|
||||
/// </summary>
|
||||
public static class HttpHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Version string for HTTP v1.0
|
||||
/// </summary>
|
||||
public const string HTTP10 = "HTTP/1.0";
|
||||
|
||||
/// <summary>
|
||||
/// Version string for HTTP v1.1
|
||||
/// </summary>
|
||||
public const string HTTP11 = "HTTP/1.1";
|
||||
|
||||
/// <summary>
|
||||
/// An empty URI
|
||||
/// </summary>
|
||||
public static readonly Uri EmptyUri = new Uri("http://localhost/");
|
||||
|
||||
/// <summary>
|
||||
/// Parses a query string.
|
||||
/// </summary>
|
||||
/// <param name="queryString">Query string (URI encoded)</param>
|
||||
/// <returns>A <see cref="HttpInput"/> object if successful; otherwise <see cref="HttpInput.Empty"/></returns>
|
||||
/// <exception cref="ArgumentNullException"><c>queryString</c> is null.</exception>
|
||||
/// <exception cref="FormatException">If string cannot be parsed.</exception>
|
||||
public static HttpInput ParseQueryString(string queryString)
|
||||
{
|
||||
if (queryString == null)
|
||||
throw new ArgumentNullException("queryString");
|
||||
if (queryString == string.Empty)
|
||||
return HttpInput.Empty;
|
||||
|
||||
HttpInput input = new HttpInput("QueryString");
|
||||
|
||||
queryString = queryString.TrimStart('?', '&');
|
||||
|
||||
// a simple value.
|
||||
if (queryString.IndexOf("&") == -1 && !queryString.Contains("%3d") && !queryString.Contains("%3D") && !queryString.Contains("="))
|
||||
{
|
||||
input.Add(string.Empty, queryString);
|
||||
return input;
|
||||
}
|
||||
|
||||
int state = 0;
|
||||
int startpos = 0;
|
||||
string name = null;
|
||||
for (int i = 0; i < queryString.Length; ++i)
|
||||
{
|
||||
int newIndexPos;
|
||||
if (state == 0 && IsEqual(queryString, ref i, out newIndexPos))
|
||||
{
|
||||
name = queryString.Substring(startpos, i - startpos);
|
||||
i = newIndexPos;
|
||||
startpos = i + 1;
|
||||
++state;
|
||||
}
|
||||
else if (state == 1 && IsAmp(queryString, ref i, out newIndexPos))
|
||||
{
|
||||
Add(input, name, queryString.Substring(startpos, i - startpos));
|
||||
i = newIndexPos;
|
||||
startpos = i + 1;
|
||||
state = 0;
|
||||
name = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == 0 && !input.GetEnumerator().MoveNext())
|
||||
throw new FormatException("Not a valid query string: " + queryString);
|
||||
|
||||
if (startpos <= queryString.Length)
|
||||
{
|
||||
if (name != null)
|
||||
Add(input, name, queryString.Substring(startpos, queryString.Length - startpos));
|
||||
else
|
||||
Add(input, string.Empty, queryString.Substring(startpos, queryString.Length - startpos));
|
||||
}
|
||||
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private static bool IsEqual(string queryStr, ref int index, out int outIndex)
|
||||
{
|
||||
outIndex = index;
|
||||
if (queryStr[index] == '=')
|
||||
return true;
|
||||
if (queryStr[index] == '%' && queryStr.Length > index + 2 && queryStr[index + 1] == '3'
|
||||
&& (queryStr[index + 2] == 'd' || queryStr[index + 2] == 'D'))
|
||||
{
|
||||
outIndex += 2;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsAmp(string queryStr, ref int index, out int outIndex)
|
||||
{
|
||||
outIndex = index;
|
||||
if (queryStr[index] == '%' && queryStr.Length > index + 2 && queryStr[index + 1] == '2' &&
|
||||
queryStr[index + 2] == '6')
|
||||
outIndex += 2;
|
||||
else if (queryStr[index] == '&')
|
||||
{
|
||||
if (queryStr.Length > index + 4
|
||||
&& (queryStr[index + 1] == 'a' || queryStr[index + 1] == 'A')
|
||||
&& (queryStr[index + 2] == 'm' || queryStr[index + 2] == 'M')
|
||||
&& (queryStr[index + 3] == 'p' || queryStr[index + 3] == 'P')
|
||||
&& queryStr[index + 4] == ';')
|
||||
outIndex += 4;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void Add(IHttpInput input, string name, string value)
|
||||
{
|
||||
input.Add(HttpUtility.UrlDecode(name), HttpUtility.UrlDecode(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains some kind of input from the browser/client.
|
||||
/// can be QueryString, form data or any other request body content.
|
||||
/// </summary>
|
||||
public class HttpInput : IHttpInput
|
||||
{
|
||||
/// <summary> Representation of a non-initialized class instance </summary>
|
||||
public static readonly HttpInput Empty = new HttpInput("Empty", true);
|
||||
private readonly IDictionary<string, HttpInputItem> _items = new Dictionary<string, HttpInputItem>();
|
||||
private string _name;
|
||||
|
||||
/// <summary> Variable telling the class that it is non-initialized <see cref="Empty"/> </summary>
|
||||
protected readonly bool _ignoreChanges;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpInput"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">form name.</param>
|
||||
public HttpInput(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpInput"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">form name.</param>
|
||||
/// <param name="ignoreChanges">if set to <c>true</c> all changes will be ignored. </param>
|
||||
/// <remarks>this constructor should only be used by Empty</remarks>
|
||||
protected HttpInput(string name, bool ignoreChanges)
|
||||
{
|
||||
_name = name;
|
||||
_ignoreChanges = ignoreChanges;
|
||||
}
|
||||
|
||||
/// <summary>Creates a deep copy of the HttpInput class</summary>
|
||||
/// <param name="input">The object to copy</param>
|
||||
/// <remarks>The function makes a deep copy of quite a lot which can be slow</remarks>
|
||||
protected HttpInput(HttpInput input)
|
||||
{
|
||||
foreach (HttpInputItem item in input)
|
||||
_items.Add(item.Name, new HttpInputItem(item));
|
||||
|
||||
_name = input._name;
|
||||
_ignoreChanges = input._ignoreChanges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Form name as lower case
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set { _name = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new element. Form array elements are parsed
|
||||
/// and added in a correct hierarchy.
|
||||
/// </summary>
|
||||
/// <param name="name">Name is converted to lower case.</param>
|
||||
/// <param name="value"></param>
|
||||
/// <exception cref="ArgumentNullException"><c>name</c> is null.</exception>
|
||||
/// <exception cref="InvalidOperationException">Cannot add stuff to <see cref="HttpInput.Empty"/>.</exception>
|
||||
public void Add(string name, string value)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException("name");
|
||||
if (_ignoreChanges)
|
||||
throw new InvalidOperationException("Cannot add stuff to HttpInput.Empty.");
|
||||
|
||||
// Check if it's a sub item.
|
||||
// we can have multiple levels of sub items as in user[extension[id]] => user -> extension -> id
|
||||
int pos = name.IndexOf('[');
|
||||
if (pos != -1)
|
||||
{
|
||||
string name1 = name.Substring(0, pos);
|
||||
string name2 = ExtractOne(name);
|
||||
if (!_items.ContainsKey(name1))
|
||||
_items.Add(name1, new HttpInputItem(name1, null));
|
||||
_items[name1].Add(name2, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_items.ContainsKey(name))
|
||||
_items[name].Add(value);
|
||||
else
|
||||
_items.Add(name, new HttpInputItem(name, value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a form item.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns>Returns <see cref="HttpInputItem.Empty"/> if item was not found.</returns>
|
||||
public HttpInputItem this[string name]
|
||||
{
|
||||
get {
|
||||
return _items.ContainsKey(name) ? _items[name] : HttpInputItem.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the class contains a <see cref="HttpInput"/> with the corresponding name.
|
||||
/// </summary>
|
||||
/// <param name="name">The field/query string name</param>
|
||||
/// <returns>True if the value exists</returns>
|
||||
public bool Contains(string name)
|
||||
{
|
||||
return _items.ContainsKey(name) && _items[name].Value != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an item and returns it.
|
||||
/// This function is primarily used to parse array items as in user[name].
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpInputItem ParseItem(string name, string value)
|
||||
{
|
||||
HttpInputItem item;
|
||||
|
||||
// Check if it's a sub item.
|
||||
// we can have multiple levels of sub items as in user[extension[id]]] => user -> extension -> id
|
||||
int pos = name.IndexOf('[');
|
||||
if (pos != -1)
|
||||
{
|
||||
string name1 = name.Substring(0, pos);
|
||||
string name2 = ExtractOne(name);
|
||||
item = new HttpInputItem(name1, null);
|
||||
item.Add(name2, value);
|
||||
}
|
||||
else
|
||||
item = new HttpInputItem(name, value);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary> Outputs the instance representing all its values joined together </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
string temp = string.Empty;
|
||||
foreach (KeyValuePair<string, HttpInputItem> item in _items)
|
||||
temp += item.Value.ToString(Name);
|
||||
return temp;
|
||||
}
|
||||
|
||||
/// <summary>Returns all items as an unescaped query string.</summary>
|
||||
/// <returns></returns>
|
||||
public string ToString(bool asQueryString)
|
||||
{
|
||||
if (!asQueryString)
|
||||
return ToString();
|
||||
|
||||
string temp = string.Empty;
|
||||
foreach (KeyValuePair<string, HttpInputItem> item in _items)
|
||||
temp += item.Value.ToString(null, true) + '&';
|
||||
|
||||
return temp.Length > 0 ? temp.Substring(0, temp.Length - 1) : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts one parameter from an array
|
||||
/// </summary>
|
||||
/// <param name="value">Containing the string array</param>
|
||||
/// <returns>All but the first value</returns>
|
||||
/// <example>
|
||||
/// string test1 = ExtractOne("system[user][extension][id]");
|
||||
/// string test2 = ExtractOne(test1);
|
||||
/// string test3 = ExtractOne(test2);
|
||||
/// // test1 = user[extension][id]
|
||||
/// // test2 = extension[id]
|
||||
/// // test3 = id
|
||||
/// </example>
|
||||
public static string ExtractOne(string value)
|
||||
{
|
||||
int pos = value.IndexOf('[');
|
||||
if (pos != -1)
|
||||
{
|
||||
++pos;
|
||||
int gotMore = value.IndexOf('[', pos + 1);
|
||||
if (gotMore != -1)
|
||||
value = value.Substring(pos, gotMore - pos - 1) + value.Substring(gotMore);
|
||||
else
|
||||
value = value.Substring(pos, value.Length - pos - 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>Resets all data contained by class</summary>
|
||||
virtual public void Clear()
|
||||
{
|
||||
_name = string.Empty;
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
///<summary>
|
||||
///Returns an enumerator that iterates through the collection.
|
||||
///</summary>
|
||||
///
|
||||
///<returns>
|
||||
///A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
|
||||
///</returns>
|
||||
///<filterpriority>1</filterpriority>
|
||||
IEnumerator<HttpInputItem> IEnumerable<HttpInputItem>.GetEnumerator()
|
||||
{
|
||||
return _items.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
|
||||
///<summary>
|
||||
///Returns an enumerator that iterates through a collection.
|
||||
///</summary>
|
||||
///
|
||||
///<returns>
|
||||
///An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
|
||||
///</returns>
|
||||
///<filterpriority>2</filterpriority>
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return _items.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for request data containers
|
||||
/// </summary>
|
||||
public interface IHttpInput : IEnumerable<HttpInputItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a parameter mapped to the presented name
|
||||
/// </summary>
|
||||
/// <param name="name">The name to map the parameter to</param>
|
||||
/// <param name="value">The parameter value</param>
|
||||
void Add(string name, string value);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a request parameter
|
||||
/// </summary>
|
||||
/// <param name="name">The name associated with the parameter</param>
|
||||
/// <returns></returns>
|
||||
HttpInputItem this[string name]
|
||||
{ get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the container contains the requested parameter
|
||||
/// </summary>
|
||||
/// <param name="name">Parameter id</param>
|
||||
/// <returns>True if parameter exists</returns>
|
||||
bool Contains(string name);
|
||||
}
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// represents a HTTP input item. Each item can have multiple sub items, a sub item
|
||||
/// is made in a HTML form by using square brackets
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// // <input type="text" name="user[FirstName]" value="jonas" /> becomes:
|
||||
/// Console.WriteLine("Value: {0}", form["user"]["FirstName"].Value);
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// All names in a form SHOULD be in lowercase.
|
||||
/// </remarks>
|
||||
public class HttpInputItem : IHttpInput
|
||||
{
|
||||
/// <summary> Representation of a non-initialized <see cref="HttpInputItem"/>.</summary>
|
||||
public static readonly HttpInputItem Empty = new HttpInputItem(string.Empty, true);
|
||||
private readonly IDictionary<string, HttpInputItem> _items = new Dictionary<string, HttpInputItem>();
|
||||
private readonly List<string> _values = new List<string>();
|
||||
private string _name;
|
||||
private readonly bool _ignoreChanges;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an input item setting its name/identifier and value
|
||||
/// </summary>
|
||||
/// <param name="name">Parameter name/id</param>
|
||||
/// <param name="value">Parameter value</param>
|
||||
public HttpInputItem(string name, string value)
|
||||
{
|
||||
Name = name;
|
||||
Add(value);
|
||||
}
|
||||
|
||||
private HttpInputItem(string name, bool ignore)
|
||||
{
|
||||
Name = name;
|
||||
_ignoreChanges = ignore;
|
||||
}
|
||||
|
||||
/// <summary>Creates a deep copy of the item specified</summary>
|
||||
/// <param name="item">The item to copy</param>
|
||||
/// <remarks>The function makes a deep copy of quite a lot which can be slow</remarks>
|
||||
public HttpInputItem(HttpInputItem item)
|
||||
{
|
||||
foreach (KeyValuePair<string, HttpInputItem> pair in item._items)
|
||||
_items.Add(pair.Key, pair.Value);
|
||||
|
||||
foreach (string value in item._values)
|
||||
_values.Add(value);
|
||||
|
||||
_ignoreChanges = item._ignoreChanges;
|
||||
_name = item.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of values
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get { return _values.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a sub item
|
||||
/// </summary>
|
||||
/// <param name="name">name in lower case.</param>
|
||||
/// <returns><see cref="HttpInputItem.Empty"/> if no item was found.</returns>
|
||||
public HttpInputItem this[string name]
|
||||
{
|
||||
get {
|
||||
return _items.ContainsKey(name) ? _items[name] : Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of item (in lower case).
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set { _name = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first value, or null if no value exist.
|
||||
/// </summary>
|
||||
public string Value
|
||||
{
|
||||
get {
|
||||
return _values.Count == 0 ? null : _values[0];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_values.Count == 0)
|
||||
_values.Add(value);
|
||||
else
|
||||
_values[0] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last value, or null if no value exist.
|
||||
/// </summary>
|
||||
public string LastValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return _values.Count == 0 ? null : _values[_values.Count - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list with values.
|
||||
/// </summary>
|
||||
public IList<string> Values
|
||||
{
|
||||
get { return _values.AsReadOnly(); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add another value to this item
|
||||
/// </summary>
|
||||
/// <param name="value">Value to add.</param>
|
||||
/// <exception cref="InvalidOperationException">Cannot add stuff to <see cref="HttpInput.Empty"/>.</exception>
|
||||
public void Add(string value)
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
if (_ignoreChanges)
|
||||
throw new InvalidOperationException("Cannot add stuff to HttpInput.Empty.");
|
||||
|
||||
_values.Add(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// checks if a sub-item exists (and has a value).
|
||||
/// </summary>
|
||||
/// <param name="name">name in lower case</param>
|
||||
/// <returns>true if the sub-item exists and has a value; otherwise false.</returns>
|
||||
public bool Contains(string name)
|
||||
{
|
||||
return _items.ContainsKey(name) && _items[name].Value != null;
|
||||
}
|
||||
|
||||
/// <summary> Returns a formatted representation of the instance with the values of all contained parameters </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the string in a formatted manner
|
||||
/// </summary>
|
||||
/// <param name="prefix">A prefix to append, used internally</param>
|
||||
/// <param name="asQuerySting">produce a query string</param>
|
||||
public string ToString(string prefix, bool asQuerySting)
|
||||
{
|
||||
string name;
|
||||
if (string.IsNullOrEmpty(prefix))
|
||||
name = Name;
|
||||
else
|
||||
name = prefix + "[" + Name + "]";
|
||||
|
||||
if (asQuerySting)
|
||||
{
|
||||
string temp;
|
||||
if (_values.Count == 0 && _items.Count > 0)
|
||||
temp = string.Empty;
|
||||
else
|
||||
temp = name;
|
||||
|
||||
if (_values.Count > 0)
|
||||
{
|
||||
temp += '=';
|
||||
foreach (string value in _values)
|
||||
temp += value + ',';
|
||||
temp = temp.Remove(temp.Length - 1, 1);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, HttpInputItem> item in _items)
|
||||
temp += item.Value.ToString(name, true) + '&';
|
||||
|
||||
return _items.Count > 0 ? temp.Substring(0, temp.Length - 1) : temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
string temp = name;
|
||||
if (_values.Count > 0)
|
||||
{
|
||||
temp += " = ";
|
||||
foreach (string value in _values)
|
||||
temp += value + ", ";
|
||||
temp = temp.Remove(temp.Length - 2, 2);
|
||||
}
|
||||
temp += Environment.NewLine;
|
||||
|
||||
foreach (KeyValuePair<string, HttpInputItem> item in _items)
|
||||
temp += item.Value.ToString(name, false);
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
#region IHttpInput Members
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="name">name in lower case</param>
|
||||
/// <returns></returns>
|
||||
HttpInputItem IHttpInput.this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _items.ContainsKey(name) ? _items[name] : Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a sub item.
|
||||
/// </summary>
|
||||
/// <param name="name">Can contain array formatting, the item is then parsed and added in multiple levels</param>
|
||||
/// <param name="value">Value to add.</param>
|
||||
/// <exception cref="ArgumentNullException">Argument is null.</exception>
|
||||
/// <exception cref="InvalidOperationException">Cannot add stuff to <see cref="HttpInput.Empty"/>.</exception>
|
||||
public void Add(string name, string value)
|
||||
{
|
||||
if (name == null && value != null)
|
||||
throw new ArgumentNullException("name");
|
||||
if (name == null)
|
||||
return;
|
||||
if (_ignoreChanges)
|
||||
throw new InvalidOperationException("Cannot add stuff to HttpInput.Empty.");
|
||||
|
||||
int pos = name.IndexOf('[');
|
||||
if (pos != -1)
|
||||
{
|
||||
string name1 = name.Substring(0, pos);
|
||||
string name2 = HttpInput.ExtractOne(name);
|
||||
if (!_items.ContainsKey(name1))
|
||||
_items.Add(name1, new HttpInputItem(name1, null));
|
||||
_items[name1].Add(name2, value);
|
||||
/*
|
||||
HttpInputItem item = HttpInput.ParseItem(name, value);
|
||||
|
||||
// Add the value to an existing sub item
|
||||
if (_items.ContainsKey(item.Name))
|
||||
_items[item.Name].Add(item.Value);
|
||||
else
|
||||
_items.Add(item.Name, item);
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_items.ContainsKey(name))
|
||||
_items[name].Add(value);
|
||||
else
|
||||
_items.Add(name, new HttpInputItem(name, value));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
///<summary>
|
||||
///Returns an enumerator that iterates through the collection.
|
||||
///</summary>
|
||||
///
|
||||
///<returns>
|
||||
///A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
|
||||
///</returns>
|
||||
///<filterpriority>1</filterpriority>
|
||||
IEnumerator<HttpInputItem> IEnumerable<HttpInputItem>.GetEnumerator()
|
||||
{
|
||||
return _items.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
///<summary>
|
||||
///Returns an enumerator that iterates through a collection.
|
||||
///</summary>
|
||||
///
|
||||
///<returns>
|
||||
///An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
|
||||
///</returns>
|
||||
///<filterpriority>2</filterpriority>
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return _items.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the string in a formatted manner
|
||||
/// </summary>
|
||||
/// <param name="prefix">A prefix to append, used internally</param>
|
||||
/// <returns></returns>
|
||||
public string ToString(string prefix)
|
||||
{
|
||||
return ToString(prefix, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// New implementation of the HTTP listener.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use the <c>Create</c> methods to create a default listener.
|
||||
/// </remarks>
|
||||
public class HttpListener : HttpListenerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// A client have been accepted, but not handled, by the listener.
|
||||
/// </summary>
|
||||
public event EventHandler<ClientAcceptedEventArgs> Accepted = delegate{};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">IP Address to accept connections on</param>
|
||||
/// <param name="port">TCP Port to listen on, default HTTP port is 80.</param>
|
||||
/// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param>
|
||||
/// <exception cref="ArgumentNullException"><c>address</c> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Port must be a positive number.</exception>
|
||||
public HttpListener(IPAddress address, int port, IHttpContextFactory factory)
|
||||
: base(address, port, factory)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">The address.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <param name="factory">The factory.</param>
|
||||
/// <param name="certificate">The certificate.</param>
|
||||
public HttpListener(IPAddress address, int port, IHttpContextFactory factory, X509Certificate certificate)
|
||||
: base(address, port, factory, certificate)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">The address.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <param name="factory">The factory.</param>
|
||||
/// <param name="certificate">The certificate.</param>
|
||||
/// <param name="protocol">The protocol.</param>
|
||||
public HttpListener(IPAddress address, int port, IHttpContextFactory factory, X509Certificate certificate, SslProtocols protocol)
|
||||
: base(address, port, factory, certificate, protocol)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HttpListener"/> instance with default factories.
|
||||
/// </summary>
|
||||
/// <param name="address">Address that the listener should accept connections on.</param>
|
||||
/// <param name="port">Port that listener should accept connections on.</param>
|
||||
/// <returns>Created HTTP listener.</returns>
|
||||
public static HttpListener Create(IPAddress address, int port)
|
||||
{
|
||||
RequestParserFactory requestFactory = new RequestParserFactory();
|
||||
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory);
|
||||
return new HttpListener(address, port, factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HttpListener"/> instance with default factories.
|
||||
/// </summary>
|
||||
/// <param name="address">Address that the listener should accept connections on.</param>
|
||||
/// <param name="port">Port that listener should accept connections on.</param>
|
||||
/// <param name="certificate">Certificate to use</param>
|
||||
/// <returns>Created HTTP listener.</returns>
|
||||
public static HttpListener Create(IPAddress address, int port, X509Certificate certificate)
|
||||
{
|
||||
RequestParserFactory requestFactory = new RequestParserFactory();
|
||||
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory);
|
||||
return new HttpListener(address, port, factory, certificate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HttpListener"/> instance with default factories.
|
||||
/// </summary>
|
||||
/// <param name="address">Address that the listener should accept connections on.</param>
|
||||
/// <param name="port">Port that listener should accept connections on.</param>
|
||||
/// <param name="certificate">Certificate to use</param>
|
||||
/// <param name="protocol">which HTTPS protocol to use, default is TLS.</param>
|
||||
/// <returns>Created HTTP listener.</returns>
|
||||
public static HttpListener Create(IPAddress address, int port, X509Certificate certificate, SslProtocols protocol)
|
||||
{
|
||||
RequestParserFactory requestFactory = new RequestParserFactory();
|
||||
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory);
|
||||
return new HttpListener(address, port, factory, certificate, protocol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to create filtering of new connections.
|
||||
/// </summary>
|
||||
/// <param name="socket">Accepted socket</param>
|
||||
/// <returns>
|
||||
/// true if connection can be accepted; otherwise false.
|
||||
/// </returns>
|
||||
protected override bool OnAcceptingSocket(Socket socket)
|
||||
{
|
||||
ClientAcceptedEventArgs args = new ClientAcceptedEventArgs(socket);
|
||||
Accepted(this, args);
|
||||
return !args.Revoked;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
public class HttpListener2
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP Listener waits for HTTP connections and provide us with <see cref="HttpListenerContext"/>s using the
|
||||
/// <see cref="RequestHandler"/> delegate.
|
||||
/// </summary>
|
||||
public class HttpListener
|
||||
{
|
||||
private readonly IPAddress _address;
|
||||
private readonly X509Certificate _certificate;
|
||||
private readonly int _port;
|
||||
private readonly SslProtocols _sslProtocol = SslProtocols.Tls;
|
||||
private ClientDisconnectedHandler _disconnectHandler;
|
||||
private TcpListener _listener;
|
||||
private ILogWriter _logWriter = NullLogWriter.Instance;
|
||||
private RequestReceivedHandler _requestHandler;
|
||||
private readonly object _listenLock = new object();
|
||||
private bool _canListen;
|
||||
private bool _useTraceLogs;
|
||||
|
||||
/// <summary>
|
||||
/// This event should be used to validate the incoming connection, i.e. to determine
|
||||
/// if it can be handled or not.
|
||||
/// </summary>
|
||||
public event EventHandler<ClientAcceptedEventArgs> SocketAccepted = delegate { };
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Listen for regular HTTP connections
|
||||
/// </summary>
|
||||
/// <param name="address">IP Address to accept connections on</param>
|
||||
/// <param name="port">TCP Port to listen on, default HTTP port is 80.</param>
|
||||
public HttpListener(IPAddress address, int port)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException("address");
|
||||
if (port <= 0)
|
||||
throw new ArgumentException("Port must be a positive number.");
|
||||
|
||||
_address = address;
|
||||
_port = port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launch HttpListener in SSL mode
|
||||
/// </summary>
|
||||
/// <param name="address">IP Address to accept connections on</param>
|
||||
/// <param name="port">TCP Port to listen on, default HTTPS port is 443</param>
|
||||
/// <param name="certificate">Certificate to use</param>
|
||||
public HttpListener(IPAddress address, int port, X509Certificate certificate)
|
||||
: this(address, port)
|
||||
{
|
||||
_certificate = certificate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launch HttpListener in SSL mode
|
||||
/// </summary>
|
||||
/// <param name="address">IP Address to accept connections on</param>
|
||||
/// <param name="port">TCP Port to listen on, default HTTPS port is 443</param>
|
||||
/// <param name="certificate">Certificate to use</param>
|
||||
/// <param name="protocol">which HTTPS protocol to use, default is TLS.</param>
|
||||
public HttpListener(IPAddress address, int port, X509Certificate certificate, SslProtocols protocol)
|
||||
: this(address, port, certificate)
|
||||
{
|
||||
_sslProtocol = protocol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gives you a change to receive log entries for all internals of the HTTP library.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You may not switch log writer after starting the listener.
|
||||
/// </remarks>
|
||||
public ILogWriter LogWriter
|
||||
{
|
||||
get { return _logWriter; }
|
||||
set
|
||||
{
|
||||
_logWriter = value ?? NullLogWriter.Instance;
|
||||
if (_certificate != null)
|
||||
_logWriter.Write(this, LogPrio.Info,
|
||||
"HTTPS(" + _sslProtocol + ") listening on " + _address + ":" + _port);
|
||||
else
|
||||
_logWriter.Write(this, LogPrio.Info, "HTTP listening on " + _address + ":" + _port);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This handler will be invoked each time a new connection is accepted.
|
||||
/// </summary>
|
||||
public RequestReceivedHandler RequestHandler
|
||||
{
|
||||
get { return _requestHandler; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
_requestHandler = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if we should turn on trace logs.
|
||||
/// </summary>
|
||||
public bool UseTraceLogs
|
||||
{
|
||||
get { return _useTraceLogs; }
|
||||
set { _useTraceLogs = value; }
|
||||
}
|
||||
|
||||
|
||||
private void OnAccept(IAsyncResult ar)
|
||||
{
|
||||
try
|
||||
{
|
||||
// i'm not trying to avoid ALL cases here. but just the most simple ones.
|
||||
// doing a lock would defeat the purpose since only one socket could be accepted at once.
|
||||
|
||||
// the lock kills performance and that's why I temporarly disabled it.
|
||||
// right now it's up to the exception block to handle Stop()
|
||||
/*lock (_listenLock)
|
||||
if (!_canListen)
|
||||
return;
|
||||
*/
|
||||
Socket socket = _listener.EndAcceptSocket(ar);
|
||||
_listener.BeginAcceptSocket(OnAccept, null);
|
||||
|
||||
ClientAcceptedEventArgs args = new ClientAcceptedEventArgs(socket);
|
||||
Accepted(this, args);
|
||||
if (args.Revoked)
|
||||
{
|
||||
_logWriter.Write(this, LogPrio.Debug, "Socket was revoked by event handler.");
|
||||
socket.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
_logWriter.Write(this, LogPrio.Debug, "Accepted connection from: " + socket.RemoteEndPoint);
|
||||
|
||||
NetworkStream stream = new NetworkStream(socket, true);
|
||||
IPEndPoint remoteEndPoint = (IPEndPoint)socket.RemoteEndPoint;
|
||||
|
||||
if (_certificate != null)
|
||||
CreateSecureContext(stream, remoteEndPoint);
|
||||
else
|
||||
new HttpClientContextImp(false, remoteEndPoint, _requestHandler, _disconnectHandler, stream,
|
||||
LogWriter);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
if (err is ObjectDisposedException || err is NullReferenceException) // occurs when we shut down the listener.
|
||||
{
|
||||
if (UseTraceLogs)
|
||||
_logWriter.Write(this, LogPrio.Trace, err.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
_logWriter.Write(this, LogPrio.Debug, err.Message);
|
||||
if (ExceptionThrown == null)
|
||||
#if DEBUG
|
||||
throw;
|
||||
#else
|
||||
_logWriter.Write(this, LogPrio.Fatal, err.Message);
|
||||
// we can't really do anything but close the connection
|
||||
#endif
|
||||
if (ExceptionThrown != null)
|
||||
ExceptionThrown(this, err);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSecureContext(Stream stream, IPEndPoint remoteEndPoint)
|
||||
{
|
||||
SslStream sslStream = new SslStream(stream, false);
|
||||
try
|
||||
{
|
||||
sslStream.AuthenticateAsServer(_certificate, false, _sslProtocol, false); //todo: this may fail
|
||||
new HttpClientContextImp(true, remoteEndPoint, _requestHandler, _disconnectHandler, sslStream,
|
||||
LogWriter);
|
||||
}
|
||||
catch (IOException err)
|
||||
{
|
||||
if (UseTraceLogs)
|
||||
_logWriter.Write(this, LogPrio.Trace, err.Message);
|
||||
}
|
||||
catch (ObjectDisposedException err)
|
||||
{
|
||||
if (UseTraceLogs)
|
||||
_logWriter.Write(this, LogPrio.Trace, err.Message);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Start listen for new connections
|
||||
/// </summary>
|
||||
/// <param name="backlog">Number of connections that can stand in a queue to be accepted.</param>
|
||||
public void Start(int backlog)
|
||||
{
|
||||
if (_listener == null)
|
||||
{
|
||||
_listener = new TcpListener(_address, _port);
|
||||
_listener.Start(backlog);
|
||||
_canListen = true;
|
||||
}
|
||||
_listener.BeginAcceptSocket(OnAccept, null);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Stop the listener
|
||||
/// </summary>
|
||||
/// <exception cref="SocketException"></exception>
|
||||
public void Stop()
|
||||
{
|
||||
lock (_listenLock)
|
||||
_canListen = false;
|
||||
_listener.Stop();
|
||||
_listener = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Let's to receive unhandled exceptions from the threads.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Exceptions will be thrown during debug mode if this event is not used,
|
||||
/// exceptions will be printed to console and suppressed during release mode.
|
||||
/// </remarks>
|
||||
public event ExceptionHandler ExceptionThrown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains a listener that doesn't do anything with the connections.
|
||||
/// </summary>
|
||||
public abstract class HttpListenerBase
|
||||
{
|
||||
private readonly IPAddress _address;
|
||||
private readonly X509Certificate _certificate;
|
||||
private readonly IHttpContextFactory _factory;
|
||||
private readonly int _port;
|
||||
private readonly ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
|
||||
private readonly SslProtocols _sslProtocol = SslProtocols.Tls | SslProtocols.Ssl3 |SslProtocols.Ssl2;
|
||||
private TcpListener _listener;
|
||||
private ILogWriter _logWriter = NullLogWriter.Instance;
|
||||
private int _pendingAccepts;
|
||||
private bool _shutdown;
|
||||
protected RemoteCertificateValidationCallback _clientCallback = null;
|
||||
|
||||
/// <summary>
|
||||
/// Listen for regular HTTP connections
|
||||
/// </summary>
|
||||
/// <param name="address">IP Address to accept connections on</param>
|
||||
/// <param name="port">TCP Port to listen on, default HTTP port is 80.</param>
|
||||
/// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param>
|
||||
/// <exception cref="ArgumentNullException"><c>address</c> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Port must be a positive number.</exception>
|
||||
protected HttpListenerBase(IPAddress address, int port, IHttpContextFactory factory)
|
||||
{
|
||||
Check.Require(address, "address");
|
||||
Check.Min(1, port, "port");
|
||||
Check.Require(factory, "factory");
|
||||
|
||||
_address = address;
|
||||
_port = port;
|
||||
_factory = factory;
|
||||
_factory.RequestReceived += OnRequestReceived;
|
||||
}
|
||||
|
||||
private void OnRequestReceived(object sender, RequestEventArgs e)
|
||||
{
|
||||
RequestReceived(sender, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpListenerBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">IP Address to accept connections on</param>
|
||||
/// <param name="port">TCP Port to listen on, default HTTPS port is 443</param>
|
||||
/// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param>
|
||||
/// <param name="certificate">Certificate to use</param>
|
||||
protected HttpListenerBase(IPAddress address, int port, IHttpContextFactory factory, X509Certificate certificate)
|
||||
: this(address, port, factory)
|
||||
{
|
||||
Check.Require(certificate, "certificate");
|
||||
|
||||
_certificate = certificate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpListenerBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">IP Address to accept connections on</param>
|
||||
/// <param name="port">TCP Port to listen on, default HTTPS port is 443</param>
|
||||
/// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param>
|
||||
/// <param name="certificate">Certificate to use</param>
|
||||
/// <param name="protocol">which HTTPS protocol to use, default is TLS.</param>
|
||||
protected HttpListenerBase(IPAddress address, int port, IHttpContextFactory factory, X509Certificate certificate,
|
||||
SslProtocols protocol)
|
||||
: this(address, port, factory, certificate)
|
||||
{
|
||||
_sslProtocol = protocol;
|
||||
}
|
||||
|
||||
|
||||
public RemoteCertificateValidationCallback CertificateValidationCallback
|
||||
{
|
||||
set {_clientCallback = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gives you a change to receive log entries for all internals of the HTTP library.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You may not switch log writer after starting the listener.
|
||||
/// </remarks>
|
||||
public ILogWriter LogWriter
|
||||
{
|
||||
get { return _logWriter; }
|
||||
set
|
||||
{
|
||||
_logWriter = value ?? NullLogWriter.Instance;
|
||||
if (_certificate != null)
|
||||
_logWriter.Write(this, LogPrio.Info,
|
||||
"HTTPS(" + _sslProtocol + ") listening on " + _address + ":" + _port);
|
||||
else
|
||||
_logWriter.Write(this, LogPrio.Info, "HTTP listening on " + _address + ":" + _port);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if we should turn on trace logs.
|
||||
/// </summary>
|
||||
public bool UseTraceLogs { get; set; }
|
||||
|
||||
|
||||
/// <exception cref="Exception"><c>Exception</c>.</exception>
|
||||
private void OnAccept(IAsyncResult ar)
|
||||
{
|
||||
bool beginAcceptCalled = false;
|
||||
try
|
||||
{
|
||||
int count = Interlocked.Decrement(ref _pendingAccepts);
|
||||
if (_shutdown)
|
||||
{
|
||||
if (count == 0)
|
||||
_shutdownEvent.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref _pendingAccepts);
|
||||
_listener.BeginAcceptSocket(OnAccept, null);
|
||||
beginAcceptCalled = true;
|
||||
Socket socket = _listener.EndAcceptSocket(ar);
|
||||
|
||||
if (!OnAcceptingSocket(socket))
|
||||
{
|
||||
socket.Disconnect(true);
|
||||
return;
|
||||
}
|
||||
|
||||
_logWriter.Write(this, LogPrio.Debug, "Accepted connection from: " + socket.RemoteEndPoint);
|
||||
|
||||
if (_certificate != null)
|
||||
_factory.CreateSecureContext(socket, _certificate, _sslProtocol, _clientCallback);
|
||||
else
|
||||
_factory.CreateContext(socket);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logWriter.Write(this, LogPrio.Debug, err.Message);
|
||||
if (ExceptionThrown == null)
|
||||
#if DEBUG
|
||||
throw;
|
||||
#else
|
||||
_logWriter.Write(this, LogPrio.Fatal, err.Message);
|
||||
// we can't really do anything but close the connection
|
||||
#endif
|
||||
if (ExceptionThrown != null)
|
||||
ExceptionThrown(this, err);
|
||||
|
||||
if (!beginAcceptCalled)
|
||||
RetryBeginAccept();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will try to accept connections one more time.
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">If any exceptions is thrown.</exception>
|
||||
private void RetryBeginAccept()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logWriter.Write(this, LogPrio.Error, "Trying to accept connections again.");
|
||||
_listener.BeginAcceptSocket(OnAccept, null);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logWriter.Write(this, LogPrio.Fatal, err.Message);
|
||||
if (ExceptionThrown == null)
|
||||
#if DEBUG
|
||||
throw;
|
||||
#else
|
||||
_logWriter.Write(this, LogPrio.Fatal, err.Message);
|
||||
// we can't really do anything but close the connection
|
||||
#endif
|
||||
if (ExceptionThrown != null)
|
||||
ExceptionThrown(this, err);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to create filtering of new connections.
|
||||
/// </summary>
|
||||
/// <param name="socket">Accepted socket</param>
|
||||
/// <returns>true if connection can be accepted; otherwise false.</returns>
|
||||
protected abstract bool OnAcceptingSocket(Socket socket);
|
||||
|
||||
/// <summary>
|
||||
/// Start listen for new connections
|
||||
/// </summary>
|
||||
/// <param name="backlog">Number of connections that can stand in a queue to be accepted.</param>
|
||||
/// <exception cref="InvalidOperationException">Listener have already been started.</exception>
|
||||
public void Start(int backlog)
|
||||
{
|
||||
if (_listener != null)
|
||||
throw new InvalidOperationException("Listener have already been started.");
|
||||
|
||||
_listener = new TcpListener(_address, _port);
|
||||
_listener.Start(backlog);
|
||||
Interlocked.Increment(ref _pendingAccepts);
|
||||
_listener.BeginAcceptSocket(OnAccept, null);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Stop the listener
|
||||
/// </summary>
|
||||
/// <exception cref="SocketException"></exception>
|
||||
public void Stop()
|
||||
{
|
||||
_shutdown = true;
|
||||
_factory.Shutdown();
|
||||
_listener.Stop();
|
||||
if (!_shutdownEvent.WaitOne())
|
||||
_logWriter.Write(this, LogPrio.Error, "Failed to shutdown listener properly.");
|
||||
_listener = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Catch exceptions not handled by the listener.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Exceptions will be thrown during debug mode if this event is not used,
|
||||
/// exceptions will be printed to console and suppressed during release mode.
|
||||
/// </remarks>
|
||||
public event ExceptionHandler ExceptionThrown = delegate { };
|
||||
|
||||
/// <summary>
|
||||
/// A request have been received from a <see cref="IHttpClientContext"/>.
|
||||
/// </summary>
|
||||
public event EventHandler<RequestEventArgs> RequestReceived = delegate{};
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.Sessions;
|
||||
|
||||
namespace HttpServer.HttpModules
|
||||
{
|
||||
/// <summary>
|
||||
/// The purpose of this module is to serve files.
|
||||
/// </summary>
|
||||
public class FileModule : HttpModule
|
||||
{
|
||||
private readonly string _baseUri;
|
||||
private readonly string _basePath;
|
||||
private readonly bool _useLastModifiedHeader;
|
||||
private readonly IDictionary<string, string> _mimeTypes = new Dictionary<string, string>();
|
||||
private static readonly string[] DefaultForbiddenChars = new[]{ "\\", "..", ":" };
|
||||
private string[] _forbiddenChars;
|
||||
private static readonly string PathSeparator = Path.DirectorySeparatorChar.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileModule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseUri">Uri to serve, for instance "/files/"</param>
|
||||
/// <param name="basePath">Path on hard drive where we should start looking for files</param>
|
||||
/// <param name="useLastModifiedHeader">If true a Last-Modifed header will be sent upon requests urging web browser to cache files</param>
|
||||
public FileModule(string baseUri, string basePath, bool useLastModifiedHeader)
|
||||
{
|
||||
Check.Require(baseUri, "baseUri");
|
||||
Check.Require(basePath, "basePath");
|
||||
|
||||
_useLastModifiedHeader = useLastModifiedHeader;
|
||||
_baseUri = baseUri;
|
||||
_basePath = basePath;
|
||||
if (!_basePath.EndsWith(PathSeparator))
|
||||
_basePath += PathSeparator;
|
||||
ForbiddenChars = DefaultForbiddenChars;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileModule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseUri">Uri to serve, for instance "/files/"</param>
|
||||
/// <param name="basePath">Path on hard drive where we should start looking for files</param>
|
||||
public FileModule(string baseUri, string basePath)
|
||||
: this(baseUri, basePath, false)
|
||||
{}
|
||||
|
||||
/// <summary>
|
||||
/// List with all mime-type that are allowed.
|
||||
/// </summary>
|
||||
/// <remarks>All other mime types will result in a Forbidden http status code.</remarks>
|
||||
public IDictionary<string, string> MimeTypes
|
||||
{
|
||||
get { return _mimeTypes; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// characters that may not exist in a path.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// fileMod.ForbiddenChars = new string[]{ "\\", "..", ":" };
|
||||
/// </example>
|
||||
public string[] ForbiddenChars
|
||||
{
|
||||
get { return _forbiddenChars; }
|
||||
set { _forbiddenChars = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Mimtypes that this class can handle per default
|
||||
/// </summary>
|
||||
public void AddDefaultMimeTypes()
|
||||
{
|
||||
MimeTypes.Add("default", "application/octet-stream");
|
||||
MimeTypes.Add("txt", "text/plain");
|
||||
MimeTypes.Add("html", "text/html");
|
||||
MimeTypes.Add("htm", "text/html");
|
||||
MimeTypes.Add("jpg", "image/jpg");
|
||||
MimeTypes.Add("jpeg", "image/jpg");
|
||||
MimeTypes.Add("bmp", "image/bmp");
|
||||
MimeTypes.Add("gif", "image/gif");
|
||||
MimeTypes.Add("png", "image/png");
|
||||
|
||||
MimeTypes.Add("ico", "image/vnd.microsoft.icon");
|
||||
MimeTypes.Add("css", "text/css");
|
||||
MimeTypes.Add("gzip", "application/x-gzip");
|
||||
MimeTypes.Add("zip", "multipart/x-zip");
|
||||
MimeTypes.Add("tar", "application/x-tar");
|
||||
MimeTypes.Add("pdf", "application/pdf");
|
||||
MimeTypes.Add("rtf", "application/rtf");
|
||||
MimeTypes.Add("xls", "application/vnd.ms-excel");
|
||||
MimeTypes.Add("ppt", "application/vnd.ms-powerpoint");
|
||||
MimeTypes.Add("doc", "application/application/msword");
|
||||
MimeTypes.Add("js", "application/javascript");
|
||||
MimeTypes.Add("au", "audio/basic");
|
||||
MimeTypes.Add("snd", "audio/basic");
|
||||
MimeTypes.Add("es", "audio/echospeech");
|
||||
MimeTypes.Add("mp3", "audio/mpeg");
|
||||
MimeTypes.Add("mp2", "audio/mpeg");
|
||||
MimeTypes.Add("mid", "audio/midi");
|
||||
MimeTypes.Add("wav", "audio/x-wav");
|
||||
MimeTypes.Add("swf", "application/x-shockwave-flash");
|
||||
MimeTypes.Add("avi", "video/avi");
|
||||
MimeTypes.Add("rm", "audio/x-pn-realaudio");
|
||||
MimeTypes.Add("ram", "audio/x-pn-realaudio");
|
||||
MimeTypes.Add("aif", "audio/x-aiff");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the request should be handled by this module.
|
||||
/// Invoked by the <see cref="HttpServer"/>
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns>true if this module should handle it.</returns>
|
||||
public bool CanHandle(Uri uri)
|
||||
{
|
||||
if (Contains(uri.AbsolutePath, _forbiddenChars))
|
||||
return false;
|
||||
|
||||
return uri.AbsolutePath.StartsWith(_baseUri) && File.Exists(GetPath(uri));
|
||||
}
|
||||
|
||||
/// <exception cref="BadRequestException">Illegal path</exception>
|
||||
private string GetPath(Uri uri)
|
||||
{
|
||||
if (Contains(uri.AbsolutePath, _forbiddenChars))
|
||||
throw new BadRequestException("Illegal path");
|
||||
|
||||
string path = _basePath + uri.AbsolutePath.Substring(_baseUri.Length);
|
||||
return path.Replace('/', Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// check if source contains any of the chars.
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="chars"></param>
|
||||
/// <returns></returns>
|
||||
private static bool Contains(string source, IEnumerable<string> chars)
|
||||
{
|
||||
foreach (string s in chars)
|
||||
{
|
||||
if (source.Contains(s))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that process the Uri.
|
||||
/// </summary>
|
||||
/// <param name="request">Information sent by the browser about the request</param>
|
||||
/// <param name="response">Information that is being sent back to the client.</param>
|
||||
/// <param name="session">Session used to </param>
|
||||
/// <exception cref="InternalServerException">Failed to find file extension</exception>
|
||||
/// <exception cref="ForbiddenException">File type is forbidden.</exception>
|
||||
public override bool Process(IHttpRequest request, IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
if (!CanHandle(request.Uri))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
string path = GetPath(request.Uri);
|
||||
string extension = GetFileExtension(path);
|
||||
if (extension == null)
|
||||
throw new InternalServerException("Failed to find file extension");
|
||||
|
||||
if (MimeTypes.ContainsKey(extension))
|
||||
response.ContentType = MimeTypes[extension];
|
||||
else
|
||||
throw new ForbiddenException("Forbidden file type: " + extension);
|
||||
|
||||
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(request.Headers["if-modified-since"]))
|
||||
{
|
||||
DateTime lastRequest = DateTime.Parse(request.Headers["if-modified-since"]);
|
||||
if (lastRequest.CompareTo(File.GetLastWriteTime(path)) <= 0)
|
||||
response.Status = HttpStatusCode.NotModified;
|
||||
}
|
||||
|
||||
if (_useLastModifiedHeader)
|
||||
response.AddHeader("Last-modified", File.GetLastWriteTime(path).ToString("r"));
|
||||
response.ContentLength = stream.Length;
|
||||
response.SendHeaders();
|
||||
|
||||
if (request.Method != "Headers" && response.Status != HttpStatusCode.NotModified)
|
||||
{
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead = stream.Read(buffer, 0, 8192);
|
||||
while (bytesRead > 0)
|
||||
{
|
||||
response.SendBody(buffer, 0, bytesRead);
|
||||
bytesRead = stream.Read(buffer, 0, 8192);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException err)
|
||||
{
|
||||
throw new InternalServerException("Failed to proccess file.", err);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return a file extension from an absolute Uri path (or plain filename)
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetFileExtension(string uri)
|
||||
{
|
||||
int pos = uri.LastIndexOf('.');
|
||||
return pos == -1 ? null : uri.Substring(pos + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
using HttpServer.Authentication;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.Sessions;
|
||||
|
||||
namespace HttpServer.HttpModules
|
||||
{
|
||||
/// <summary>
|
||||
/// A HttpModule can be used to serve Uri's. The module itself
|
||||
/// decides if it should serve a Uri or not. In this way, you can
|
||||
/// get a very flexible http application since you can let multiple modules
|
||||
/// serve almost similar urls.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Throw <see cref="UnauthorizedException"/> if you are using a <see cref="AuthenticationModule"/> and want to prompt for user name/password.
|
||||
/// </remarks>
|
||||
public abstract class HttpModule
|
||||
{
|
||||
private ILogWriter _log = NullLogWriter.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Method that process the url
|
||||
/// </summary>
|
||||
/// <param name="request">Information sent by the browser about the request</param>
|
||||
/// <param name="response">Information that is being sent back to the client.</param>
|
||||
/// <param name="session">Session used to </param>
|
||||
/// <returns>true if this module handled the request.</returns>
|
||||
public abstract bool Process(IHttpRequest request, IHttpResponse response, IHttpSession session);
|
||||
/*
|
||||
/// <summary>
|
||||
/// Checks if authentication is required by the module.
|
||||
/// </summary>
|
||||
/// <param name="request">Information sent by the browser about the request</param>
|
||||
/// <param name="response">Information that is being sent back to the client.</param>
|
||||
/// <param name="session">Session used to </param>
|
||||
/// <param name="cookies">Incoming/outgoing cookies. If you modify a cookie, make sure that you also set a expire date. Modified cookies will automatically be sent.</param>
|
||||
/// <returns>true authentication should be used.</returns>
|
||||
public abstract bool IsAuthenticationRequired(IHttpRequest request, IHttpResponse response, IHttpSession session,
|
||||
HttpCookies cookies);
|
||||
* */
|
||||
|
||||
/// <summary>
|
||||
/// Set the log writer to use.
|
||||
/// </summary>
|
||||
/// <param name="writer">logwriter to use.</param>
|
||||
public void SetLogWriter(ILogWriter writer)
|
||||
{
|
||||
_log = writer ?? NullLogWriter.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log something.
|
||||
/// </summary>
|
||||
/// <param name="prio">importance of log message</param>
|
||||
/// <param name="message">message</param>
|
||||
protected virtual void Write(LogPrio prio, string message)
|
||||
{
|
||||
_log.Write(this, prio, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true specifies that the module doesn't consume the processing of a request so that subsequent modules
|
||||
/// can continue processing afterwards. Default is false.
|
||||
/// </summary>
|
||||
public virtual bool AllowSecondaryProcessing
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer.HttpModules
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to inform http server that
|
||||
/// </summary>
|
||||
public class HttpModuleExceptionEventArgs : EventArgs
|
||||
{
|
||||
private readonly Exception _exception;
|
||||
|
||||
/// <summary>
|
||||
/// Eventarguments used when an exception is thrown by a module
|
||||
/// </summary>
|
||||
/// <param name="e">the exception</param>
|
||||
public HttpModuleExceptionEventArgs(Exception e)
|
||||
{
|
||||
_exception = e;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown in a module
|
||||
/// </summary>
|
||||
public Exception Exception
|
||||
{
|
||||
get { return _exception; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.Helpers;
|
||||
using HttpServer.Sessions;
|
||||
|
||||
namespace HttpServer.HttpModules
|
||||
{
|
||||
/// <summary>
|
||||
/// Serves files that are stored in embedded resources.
|
||||
/// </summary>
|
||||
public class ResourceFileModule : HttpModule
|
||||
{
|
||||
private readonly ResourceManager _resourceManager;
|
||||
|
||||
private readonly IDictionary<string, string> _mimeTypes = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResourceFileModule"/> class.
|
||||
/// Runs <see cref="AddDefaultMimeTypes"/> to make sure the basic mime types are available, they can be cleared later
|
||||
/// through the use of <see cref="MimeTypes"/> if desired.
|
||||
/// </summary>
|
||||
public ResourceFileModule()
|
||||
{
|
||||
AddDefaultMimeTypes();
|
||||
_resourceManager = new ResourceManager();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResourceFileModule"/> class.
|
||||
/// Runs <see cref="AddDefaultMimeTypes"/> to make sure the basic mime types are available, they can be cleared later
|
||||
/// through the use of <see cref="MimeTypes"/> if desired.
|
||||
/// </summary>
|
||||
/// <param name="logWriter">The log writer to use when logging events</param>
|
||||
public ResourceFileModule(ILogWriter logWriter)
|
||||
{
|
||||
_resourceManager = new ResourceManager(logWriter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List with all mime-type that are allowed.
|
||||
/// </summary>
|
||||
/// <remarks>All other mime types will result in a Forbidden http status code.</remarks>
|
||||
public IDictionary<string, string> MimeTypes
|
||||
{
|
||||
get { return _mimeTypes; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mimtypes that this class can handle per default
|
||||
/// </summary>
|
||||
public void AddDefaultMimeTypes()
|
||||
{
|
||||
MimeTypes.Add("default", "application/octet-stream");
|
||||
MimeTypes.Add("txt", "text/plain");
|
||||
MimeTypes.Add("html", "text/html");
|
||||
MimeTypes.Add("htm", "text/html");
|
||||
MimeTypes.Add("jpg", "image/jpg");
|
||||
MimeTypes.Add("jpeg", "image/jpg");
|
||||
MimeTypes.Add("bmp", "image/bmp");
|
||||
MimeTypes.Add("gif", "image/gif");
|
||||
MimeTypes.Add("png", "image/png");
|
||||
|
||||
MimeTypes.Add("css", "text/css");
|
||||
MimeTypes.Add("gzip", "application/x-gzip");
|
||||
MimeTypes.Add("zip", "multipart/x-zip");
|
||||
MimeTypes.Add("tar", "application/x-tar");
|
||||
MimeTypes.Add("pdf", "application/pdf");
|
||||
MimeTypes.Add("rtf", "application/rtf");
|
||||
MimeTypes.Add("xls", "application/vnd.ms-excel");
|
||||
MimeTypes.Add("ppt", "application/vnd.ms-powerpoint");
|
||||
MimeTypes.Add("doc", "application/application/msword");
|
||||
MimeTypes.Add("js", "application/javascript");
|
||||
MimeTypes.Add("au", "audio/basic");
|
||||
MimeTypes.Add("snd", "audio/basic");
|
||||
MimeTypes.Add("es", "audio/echospeech");
|
||||
MimeTypes.Add("mp3", "audio/mpeg");
|
||||
MimeTypes.Add("mp2", "audio/mpeg");
|
||||
MimeTypes.Add("mid", "audio/midi");
|
||||
MimeTypes.Add("wav", "audio/x-wav");
|
||||
MimeTypes.Add("swf", "application/x-shockwave-flash");
|
||||
MimeTypes.Add("avi", "video/avi");
|
||||
MimeTypes.Add("rm", "audio/x-pn-realaudio");
|
||||
MimeTypes.Add("ram", "audio/x-pn-realaudio");
|
||||
MimeTypes.Add("aif", "audio/x-aiff");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads resources from a namespace in the given assembly to an uri
|
||||
/// </summary>
|
||||
/// <param name="toUri">The uri to map the resources to</param>
|
||||
/// <param name="fromAssembly">The assembly in which the resources reside</param>
|
||||
/// <param name="fromNamespace">The namespace from which to load the resources</param>
|
||||
/// <usage>
|
||||
/// resourceLoader.LoadResources("/user/", typeof(User).Assembly, "MyLib.Models.User.Views");
|
||||
///
|
||||
/// will make ie the resource MyLib.Models.User.Views.stylesheet.css accessible via /user/stylesheet.css
|
||||
/// </usage>
|
||||
/// <returns>The amount of loaded files, giving you the possibility of making sure the resources needed gets loaded</returns>
|
||||
public int AddResources(string toUri, Assembly fromAssembly, string fromNamespace)
|
||||
{
|
||||
return _resourceManager.LoadResources(toUri, fromAssembly, fromNamespace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the module can handle the request
|
||||
/// </summary>
|
||||
private bool CanHandle(IHttpRequest request)
|
||||
{
|
||||
return !request.Uri.AbsolutePath.EndsWith("*") && _resourceManager.ContainsResource(request.Uri.AbsolutePath);
|
||||
}
|
||||
|
||||
#region Overrides of HttpModule
|
||||
|
||||
/// <summary>
|
||||
/// Method that process the url
|
||||
/// </summary>
|
||||
/// <param name="request">Information sent by the browser about the request</param>
|
||||
/// <param name="response">Information that is being sent back to the client.</param>
|
||||
/// <param name="session">Session used to </param>
|
||||
/// <returns>true if this module handled the request.</returns>
|
||||
public override bool Process(IHttpRequest request, IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
if(!CanHandle(request))
|
||||
return false;
|
||||
|
||||
string path = request.Uri.AbsolutePath;
|
||||
string contentType;
|
||||
Stream resourceStream = GetResourceStream(path, out contentType);
|
||||
if(resourceStream == null)
|
||||
return false;
|
||||
|
||||
response.ContentType = contentType;
|
||||
DateTime modifiedTime = DateTime.MinValue;
|
||||
if (!string.IsNullOrEmpty(request.Headers["if-modified-since"]))
|
||||
{
|
||||
DateTime lastRequest = DateTime.Parse(request.Headers["if-modified-since"]);
|
||||
if (lastRequest.CompareTo(modifiedTime) <= 0)
|
||||
response.Status = HttpStatusCode.NotModified;
|
||||
}
|
||||
|
||||
response.AddHeader("Last-modified", modifiedTime.ToString("r"));
|
||||
response.ContentLength = resourceStream.Length;
|
||||
response.SendHeaders();
|
||||
|
||||
if (request.Method != "Headers" && response.Status != HttpStatusCode.NotModified)
|
||||
{
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead = resourceStream.Read(buffer, 0, 8192);
|
||||
while (bytesRead > 0)
|
||||
{
|
||||
response.SendBody(buffer, 0, bytesRead);
|
||||
bytesRead = resourceStream.Read(buffer, 0, 8192);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Stream GetResourceStream(string path, out string contentType)
|
||||
{
|
||||
int extensionPosition = path.LastIndexOf('.');
|
||||
string extension = extensionPosition == -1 ? null : path.Substring(extensionPosition + 1);
|
||||
if (extension == null)
|
||||
throw new InternalServerException("Failed to find file extension");
|
||||
|
||||
if (MimeTypes.ContainsKey(extension))
|
||||
contentType = MimeTypes[extension];
|
||||
else
|
||||
throw new ForbiddenException("Forbidden file type: " + extension);
|
||||
|
||||
return _resourceManager.GetResourceStream(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer;
|
||||
using HttpServer.Sessions;
|
||||
|
||||
namespace HttpServer.HttpModules
|
||||
{
|
||||
/// <summary>
|
||||
/// A reverse proxy are used to act as a bridge between local (protected/hidden) websites
|
||||
/// and public clients.
|
||||
///
|
||||
/// A typical usage is to allow web servers on non standard ports to still be available
|
||||
/// to the public clients, or allow web servers on private ips to be available.
|
||||
/// </summary>
|
||||
public class ReverseProxyModule : HttpModule
|
||||
{
|
||||
private readonly string _destinationUrl;
|
||||
private readonly string _sourceUrl;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="source">Base url requested from browser</param>
|
||||
/// <param name="destination">Base url on private web server</param>
|
||||
/// <example>
|
||||
/// // this will return contents from http://192.168.1.128/view/jonas when client requests http://www.gauffin.com/user/view/jonas
|
||||
/// _server.Add(new ReverseProxyModule("http://www.gauffin.com/user/", "http://192.168.1.128/");
|
||||
/// </example>
|
||||
public ReverseProxyModule(string source, string destination)
|
||||
{
|
||||
if (destination == null)
|
||||
throw new ArgumentNullException("destination");
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
|
||||
_destinationUrl = destination;
|
||||
if (!_destinationUrl.EndsWith("/"))
|
||||
_destinationUrl += "/";
|
||||
_sourceUrl = source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that determines if an url should be handled or not by the module
|
||||
/// </summary>
|
||||
/// <param name="uri">Url requested by the client.</param>
|
||||
/// <returns>true if module should handle the url.</returns>
|
||||
private bool CanHandle(Uri uri)
|
||||
{
|
||||
return uri.AbsolutePath.StartsWith(_sourceUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that process the url
|
||||
/// </summary>
|
||||
/// <param name="request">Information sent by the browser about the request</param>
|
||||
/// <param name="response">Information that is being sent back to the client.</param>
|
||||
/// <param name="session">Session used to </param>
|
||||
public override bool Process(IHttpRequest request, IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
if (!CanHandle(request.Uri))
|
||||
return false;
|
||||
|
||||
int pos = request.Uri.OriginalString.IndexOf(_sourceUrl);
|
||||
if (pos == -1)
|
||||
throw new InternalServerException("Failed to find source url '" + _sourceUrl + "' for proxy.");
|
||||
|
||||
string sourceUrl = request.Uri.OriginalString.Substring(0, pos + _sourceUrl.Length);
|
||||
string newUrl = request.Uri.OriginalString.Replace(sourceUrl, _destinationUrl);
|
||||
Uri url = new Uri(newUrl);
|
||||
|
||||
WebClient client = new WebClient();
|
||||
try
|
||||
{
|
||||
byte[] bytes = client.DownloadData(url);
|
||||
string content = client.ResponseHeaders[HttpResponseHeader.ContentType];
|
||||
response.ContentType = content;
|
||||
|
||||
string contentLengthStr = client.ResponseHeaders[HttpResponseHeader.ContentLength];
|
||||
int contentLength;
|
||||
int.TryParse(contentLengthStr, out contentLength);
|
||||
response.ContentLength = contentLength;
|
||||
|
||||
if (content.StartsWith("text/html"))
|
||||
{
|
||||
string data = client.Encoding.GetString(bytes);
|
||||
StreamWriter writer = new StreamWriter(response.Body, client.Encoding);
|
||||
writer.Write(data.Replace(_destinationUrl, sourceUrl).Replace("href=\"/", "href=\"" + _sourceUrl));
|
||||
writer.Write(data.Replace(_destinationUrl, sourceUrl).Replace("src=\"/", "src=\"" + _sourceUrl));
|
||||
}
|
||||
else
|
||||
response.Body.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
catch (WebException err)
|
||||
{
|
||||
throw new InternalServerException("Failed to proxy " + url, err);
|
||||
}
|
||||
catch (NotSupportedException err)
|
||||
{
|
||||
throw new InternalServerException("Failed to proxy " + url, err);
|
||||
}
|
||||
catch (IOException err)
|
||||
{
|
||||
throw new InternalServerException("Failed to proxy " + url, err);
|
||||
}
|
||||
catch (FormatException err)
|
||||
{
|
||||
throw new InternalServerException("Failed to proxy " + url, err);
|
||||
}
|
||||
catch (ObjectDisposedException err)
|
||||
{
|
||||
throw new InternalServerException("Failed to proxy " + url, err);
|
||||
}
|
||||
catch (ArgumentNullException err)
|
||||
{
|
||||
throw new InternalServerException("Failed to proxy " + url, err);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using HttpServer;
|
||||
using HttpServer.Sessions;
|
||||
|
||||
namespace HttpServer.HttpModules
|
||||
{
|
||||
/// <summary>
|
||||
/// The website module let's you handle multiple websites in the same server.
|
||||
/// It uses the "Host" header to check which site you want.
|
||||
/// </summary>
|
||||
/// <remarks>It's recommended that you do not
|
||||
/// add any other modules to HttpServer if you are using the website module. Instead,
|
||||
/// add all wanted modules to each website.</remarks>
|
||||
class WebSiteModule : HttpModule
|
||||
{
|
||||
private readonly string _host;
|
||||
readonly List<HttpModule> _modules = new List<HttpModule>();
|
||||
private readonly string _siteName;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="host">domain name that should be handled.</param>
|
||||
/// <param name="name"></param>
|
||||
public WebSiteModule(string host, string name)
|
||||
{
|
||||
_host = host;
|
||||
_siteName = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of site.
|
||||
/// </summary>
|
||||
public string SiteName
|
||||
{
|
||||
get { return _siteName; }
|
||||
}
|
||||
|
||||
public bool CanHandle(Uri uri)
|
||||
{
|
||||
return string.Compare(uri.Host, _host, true) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that process the url
|
||||
/// </summary>
|
||||
/// <param name="request">Information sent by the browser about the request</param>
|
||||
/// <param name="response">Information that is being sent back to the client.</param>
|
||||
/// <param name="session">Session used to </param>
|
||||
public override bool Process(IHttpRequest request, IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
if (!CanHandle(request.Uri))
|
||||
return false;
|
||||
|
||||
bool handled = false;
|
||||
foreach (HttpModule module in _modules)
|
||||
{
|
||||
if (module.Process(request, response, session))
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
response.Status = HttpStatusCode.NotFound;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns item either from a form or a query string (checks them in that order)
|
||||
/// </summary>
|
||||
public class HttpParam : IHttpInput
|
||||
{
|
||||
/// <summary> Representation of a non-initialized HttpParam </summary>
|
||||
public static readonly HttpParam Empty = new HttpParam(HttpInput.Empty, HttpInput.Empty);
|
||||
|
||||
private IHttpInput _form;
|
||||
private IHttpInput _query;
|
||||
|
||||
private List<HttpInputItem> _items = new List<HttpInputItem>();
|
||||
|
||||
/// <summary>Initialises the class to hold a value either from a post request or a querystring request</summary>
|
||||
public HttpParam(IHttpInput form, IHttpInput query)
|
||||
{
|
||||
_form = form;
|
||||
_query = query;
|
||||
}
|
||||
|
||||
#region IHttpInput Members
|
||||
|
||||
/// <summary>
|
||||
/// The add method is not availible for HttpParam
|
||||
/// since HttpParam checks both Request.Form and Request.QueryString
|
||||
/// </summary>
|
||||
/// <param name="name">name identifying the value</param>
|
||||
/// <param name="value">value to add</param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
[Obsolete("Not implemented for HttpParam")]
|
||||
public void Add(string name, string value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the form or querystring has the specified value
|
||||
/// </summary>
|
||||
/// <param name="name">Name, case sensitive</param>
|
||||
/// <returns>true if found; otherwise false.</returns>
|
||||
public bool Contains(string name)
|
||||
{
|
||||
return _form.Contains(name) || _query.Contains(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch an item from the form or querystring (in that order).
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns>Item if found; otherwise HttpInputItem.EmptyLanguageNode</returns>
|
||||
public HttpInputItem this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_form[name] != HttpInputItem.Empty)
|
||||
return _form[name];
|
||||
else
|
||||
return _query[name];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal void SetQueryString(HttpInput query)
|
||||
{
|
||||
_query = query;
|
||||
}
|
||||
|
||||
internal void SetForm(HttpInput form)
|
||||
{
|
||||
_form = form;
|
||||
}
|
||||
|
||||
///<summary>
|
||||
///Returns an enumerator that iterates through the collection.
|
||||
///</summary>
|
||||
///
|
||||
///<returns>
|
||||
///A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
|
||||
///</returns>
|
||||
///<filterpriority>1</filterpriority>
|
||||
IEnumerator<HttpInputItem> IEnumerable<HttpInputItem>.GetEnumerator()
|
||||
{
|
||||
List<HttpInputItem> items = new List<HttpInputItem>(_query);
|
||||
items.AddRange(_form);
|
||||
return items.GetEnumerator();
|
||||
}
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
///<summary>
|
||||
///Returns an enumerator that iterates through a collection.
|
||||
///</summary>
|
||||
///
|
||||
///<returns>
|
||||
///An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
|
||||
///</returns>
|
||||
///<filterpriority>2</filterpriority>
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
List<HttpInputItem> items = new List<HttpInputItem>(_query);
|
||||
items.AddRange(_form);
|
||||
return items.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,439 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.FormDecoders;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains server side HTTP request information.
|
||||
/// </summary>
|
||||
public class HttpRequest : IHttpRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Chars used to split an URL path into multiple parts.
|
||||
/// </summary>
|
||||
public static readonly char[] UriSplitters = new[] { '/' };
|
||||
public static uint baseID = 0;
|
||||
|
||||
private readonly NameValueCollection _headers = new NameValueCollection();
|
||||
private readonly HttpParam _param = new HttpParam(HttpInput.Empty, HttpInput.Empty);
|
||||
private Stream _body = new MemoryStream();
|
||||
private int _bodyBytesLeft;
|
||||
private ConnectionType _connection = ConnectionType.KeepAlive;
|
||||
private int _contentLength;
|
||||
private HttpForm _form = HttpForm.EmptyForm;
|
||||
private string _httpVersion = string.Empty;
|
||||
private string _method = string.Empty;
|
||||
private HttpInput _queryString = HttpInput.Empty;
|
||||
private Uri _uri = HttpHelper.EmptyUri;
|
||||
private string _uriPath;
|
||||
public readonly IHttpClientContext _context;
|
||||
|
||||
public HttpRequest(IHttpClientContext pContext)
|
||||
{
|
||||
ID = baseID++;
|
||||
_context = pContext;
|
||||
}
|
||||
|
||||
public uint ID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this <see cref="HttpRequest"/> is secure.
|
||||
/// </summary>
|
||||
public bool Secure { get; internal set; }
|
||||
|
||||
public IHttpClientContext Context { get { return _context; } }
|
||||
/// <summary>
|
||||
/// Path and query (will be merged with the host header) and put in Uri
|
||||
/// </summary>
|
||||
/// <see cref="Uri"/>
|
||||
public string UriPath
|
||||
{
|
||||
get { return _uriPath; }
|
||||
set
|
||||
{
|
||||
_uriPath = value;
|
||||
int pos = _uriPath.IndexOf('?');
|
||||
if (pos != -1)
|
||||
{
|
||||
_queryString = HttpHelper.ParseQueryString(_uriPath.Substring(pos + 1));
|
||||
_param.SetQueryString(_queryString);
|
||||
string path = _uriPath.Substring(0, pos);
|
||||
_uriPath = System.Web.HttpUtility.UrlDecode(path) + "?" + _uriPath.Substring(pos + 1);
|
||||
UriParts = value.Substring(0, pos).Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
else
|
||||
{
|
||||
_uriPath = System.Web.HttpUtility.UrlDecode(_uriPath);
|
||||
UriParts = value.Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign a form.
|
||||
/// </summary>
|
||||
/// <param name="form"></param>
|
||||
internal void AssignForm(HttpForm form)
|
||||
{
|
||||
_form = form;
|
||||
}
|
||||
|
||||
internal static bool ShouldReplyTo100Continue(IHttpRequest request)
|
||||
{
|
||||
string expectHeader = request.Headers["expect"];
|
||||
return expectHeader != null && expectHeader.Contains("100-continue");
|
||||
}
|
||||
|
||||
#region IHttpRequest Members
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the body is complete.
|
||||
/// </summary>
|
||||
public bool BodyIsComplete
|
||||
{
|
||||
get { return _bodyBytesLeft == 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets kind of types accepted by the client.
|
||||
/// </summary>
|
||||
public string[] AcceptTypes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets body stream.
|
||||
/// </summary>
|
||||
public Stream Body
|
||||
{
|
||||
get { return _body; }
|
||||
set { _body = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets kind of connection used for the session.
|
||||
/// </summary>
|
||||
public ConnectionType Connection
|
||||
{
|
||||
get { return _connection; }
|
||||
set { _connection = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets number of bytes in the body.
|
||||
/// </summary>
|
||||
public int ContentLength
|
||||
{
|
||||
get { return _contentLength; }
|
||||
set
|
||||
{
|
||||
_contentLength = value;
|
||||
_bodyBytesLeft = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets headers sent by the client.
|
||||
/// </summary>
|
||||
public NameValueCollection Headers
|
||||
{
|
||||
get { return _headers; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets version of HTTP protocol that's used.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Probably <see cref="HttpHelper.HTTP10"/> or <see cref="HttpHelper.HTTP11"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="HttpHelper"/>
|
||||
public string HttpVersion
|
||||
{
|
||||
get { return _httpVersion; }
|
||||
set { _httpVersion = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets requested method.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
/// <remarks>
|
||||
/// Will always be in upper case.
|
||||
/// </remarks>
|
||||
/// <see cref="HttpServer.Method"/>
|
||||
public string Method
|
||||
{
|
||||
get { return _method; }
|
||||
set { _method = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets variables sent in the query string
|
||||
/// </summary>
|
||||
public HttpInput QueryString
|
||||
{
|
||||
get { return _queryString; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets requested URI.
|
||||
/// </summary>
|
||||
public Uri Uri
|
||||
{
|
||||
get { return _uri; }
|
||||
set
|
||||
{
|
||||
_uri = value ?? HttpHelper.EmptyUri;
|
||||
UriParts = _uri.AbsolutePath.Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uri absolute path splitted into parts.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// // uri is: http://gauffin.com/code/tiny/
|
||||
/// Console.WriteLine(request.UriParts[0]); // result: code
|
||||
/// Console.WriteLine(request.UriParts[1]); // result: tiny
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// If you're using controllers than the first part is controller name,
|
||||
/// the second part is method name and the third part is Id property.
|
||||
/// </remarks>
|
||||
/// <seealso cref="Uri"/>
|
||||
public string[] UriParts { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets parameter from <see cref="QueryString"/> or <see cref="Form"/>.
|
||||
/// </summary>
|
||||
public HttpParam Param
|
||||
{
|
||||
get { return _param; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets form parameters.
|
||||
/// </summary>
|
||||
public HttpForm Form
|
||||
{
|
||||
get { return _form; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the request was made by Ajax (Asynchronous JavaScript)
|
||||
/// </summary>
|
||||
public bool IsAjax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets cookies that was sent with the request.
|
||||
/// </summary>
|
||||
public RequestCookies Cookies { get; private set; }
|
||||
|
||||
///<summary>
|
||||
///Creates a new object that is a copy of the current instance.
|
||||
///</summary>
|
||||
///
|
||||
///<returns>
|
||||
///A new object that is a copy of this instance.
|
||||
///</returns>
|
||||
///<filterpriority>2</filterpriority>
|
||||
public object Clone()
|
||||
{
|
||||
// this method was mainly created for testing.
|
||||
// dont use it that much...
|
||||
var request = new HttpRequest(Context);
|
||||
request.Method = _method;
|
||||
if (AcceptTypes != null)
|
||||
{
|
||||
request.AcceptTypes = new string[AcceptTypes.Length];
|
||||
AcceptTypes.CopyTo(request.AcceptTypes, 0);
|
||||
}
|
||||
request._httpVersion = _httpVersion;
|
||||
request._queryString = _queryString;
|
||||
request.Uri = _uri;
|
||||
|
||||
var buffer = new byte[_body.Length];
|
||||
_body.Read(buffer, 0, (int)_body.Length);
|
||||
request.Body = new MemoryStream();
|
||||
request.Body.Write(buffer, 0, buffer.Length);
|
||||
request.Body.Seek(0, SeekOrigin.Begin);
|
||||
request.Body.Flush();
|
||||
|
||||
request._headers.Clear();
|
||||
foreach (string key in _headers)
|
||||
{
|
||||
string[] values = _headers.GetValues(key);
|
||||
if (values != null)
|
||||
foreach (string value in values)
|
||||
request.AddHeader(key, value);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode body into a form.
|
||||
/// </summary>
|
||||
/// <param name="providers">A list with form decoders.</param>
|
||||
/// <exception cref="InvalidDataException">If body contents is not valid for the chosen decoder.</exception>
|
||||
/// <exception cref="InvalidOperationException">If body is still being transferred.</exception>
|
||||
public void DecodeBody(FormDecoderProvider providers)
|
||||
{
|
||||
if (_bodyBytesLeft > 0)
|
||||
throw new InvalidOperationException("Body have not yet been completed.");
|
||||
|
||||
_form = providers.Decode(_headers["content-type"], _body, Encoding.UTF8);
|
||||
if (_form != HttpInput.Empty)
|
||||
_param.SetForm(_form);
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Cookies
|
||||
///</summary>
|
||||
///<param name="cookies">the cookies</param>
|
||||
public void SetCookies(RequestCookies cookies)
|
||||
{
|
||||
Cookies = cookies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a response object.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="IHttpResponse"/>.</returns>
|
||||
public IHttpResponse CreateResponse(IHttpClientContext context)
|
||||
{
|
||||
return new HttpResponse(context, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during parsing of a <see cref="IHttpRequest"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the header, should not be URL encoded</param>
|
||||
/// <param name="value">Value of the header, should not be URL encoded</param>
|
||||
/// <exception cref="BadRequestException">If a header is incorrect.</exception>
|
||||
public void AddHeader(string name, string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new BadRequestException("Invalid header name: " + name ?? "<null>");
|
||||
if (string.IsNullOrEmpty(value))
|
||||
throw new BadRequestException("Header '" + name + "' do not contain a value.");
|
||||
|
||||
name = name.ToLowerInvariant();
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case "http_x_requested_with":
|
||||
case "x-requested-with":
|
||||
if (string.Compare(value, "XMLHttpRequest", true) == 0)
|
||||
IsAjax = true;
|
||||
break;
|
||||
case "accept":
|
||||
AcceptTypes = value.Split(',');
|
||||
for (int i = 0; i < AcceptTypes.Length; ++i)
|
||||
AcceptTypes[i] = AcceptTypes[i].Trim();
|
||||
break;
|
||||
case "content-length":
|
||||
int t;
|
||||
if (!int.TryParse(value, out t))
|
||||
throw new BadRequestException("Invalid content length.");
|
||||
ContentLength = t;
|
||||
break; //todo: maybe throw an exception
|
||||
case "host":
|
||||
try
|
||||
{
|
||||
_uri = new Uri(Secure ? "https://" : "http://" + value + _uriPath);
|
||||
UriParts = _uri.AbsolutePath.Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
catch (UriFormatException err)
|
||||
{
|
||||
throw new BadRequestException("Failed to parse uri: " + value + _uriPath, err);
|
||||
}
|
||||
break;
|
||||
case "remote_addr":
|
||||
// to prevent hacking (since it's added by IHttpClientContext before parsing).
|
||||
if (_headers[name] == null)
|
||||
_headers.Add(name, value);
|
||||
break;
|
||||
|
||||
case "connection":
|
||||
if (string.Compare(value, "close", true) == 0)
|
||||
Connection = ConnectionType.Close;
|
||||
else if (value.StartsWith("keep-alive", StringComparison.CurrentCultureIgnoreCase))
|
||||
Connection = ConnectionType.KeepAlive;
|
||||
else if (value.StartsWith("Upgrade", StringComparison.CurrentCultureIgnoreCase))
|
||||
Connection = ConnectionType.KeepAlive;
|
||||
else
|
||||
throw new BadRequestException("Unknown 'Connection' header type.");
|
||||
break;
|
||||
|
||||
case "expect":
|
||||
if (value.Contains("100-continue"))
|
||||
{
|
||||
|
||||
}
|
||||
_headers.Add(name, value);
|
||||
break;
|
||||
|
||||
default:
|
||||
_headers.Add(name, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add bytes to the body
|
||||
/// </summary>
|
||||
/// <param name="bytes">buffer to read bytes from</param>
|
||||
/// <param name="offset">where to start read</param>
|
||||
/// <param name="length">number of bytes to read</param>
|
||||
/// <returns>Number of bytes actually read (same as length unless we got all body bytes).</returns>
|
||||
/// <exception cref="InvalidOperationException">If body is not writable</exception>
|
||||
/// <exception cref="ArgumentNullException"><c>bytes</c> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><c>offset</c> is out of range.</exception>
|
||||
public int AddToBody(byte[] bytes, int offset, int length)
|
||||
{
|
||||
if (bytes == null)
|
||||
throw new ArgumentNullException("bytes");
|
||||
if (offset + length > bytes.Length)
|
||||
throw new ArgumentOutOfRangeException("offset");
|
||||
if (length == 0)
|
||||
return 0;
|
||||
if (!_body.CanWrite)
|
||||
throw new InvalidOperationException("Body is not writable.");
|
||||
|
||||
if (length > _bodyBytesLeft)
|
||||
{
|
||||
length = _bodyBytesLeft;
|
||||
}
|
||||
|
||||
_body.Write(bytes, offset, length);
|
||||
_bodyBytesLeft -= length;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear everything in the request
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
if (_body != null && _body.CanRead)
|
||||
_body.Dispose();
|
||||
_body = null;
|
||||
_contentLength = 0;
|
||||
_method = string.Empty;
|
||||
_uri = HttpHelper.EmptyUri;
|
||||
_queryString = HttpInput.Empty;
|
||||
_bodyBytesLeft = 0;
|
||||
_headers.Clear();
|
||||
_connection = ConnectionType.KeepAlive;
|
||||
IsAjax = false;
|
||||
_form.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,675 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
public class HttpResponse : IHttpResponse
|
||||
{
|
||||
private const string DefaultContentType = "text/html;charset=UTF-8";
|
||||
private readonly IHttpClientContext m_context;
|
||||
private readonly ResponseCookies _cookies = new ResponseCookies();
|
||||
private readonly NameValueCollection m_headers = new NameValueCollection();
|
||||
private string _httpVersion;
|
||||
private Stream _body;
|
||||
private long _contentLength;
|
||||
private string _contentType;
|
||||
private Encoding _encoding = Encoding.UTF8;
|
||||
private int _keepAlive = 60;
|
||||
public uint requestID { get; private set; }
|
||||
public byte[] RawBuffer { get; set; }
|
||||
public int RawBufferStart { get; set; }
|
||||
public int RawBufferLen { get; set; }
|
||||
|
||||
internal byte[] m_headerBytes = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IHttpResponse"/> class.
|
||||
/// </summary>
|
||||
/// <param name="context">Client that send the <see cref="IHttpRequest"/>.</param>
|
||||
/// <param name="request">Contains information of what the client want to receive.</param>
|
||||
/// <exception cref="ArgumentException"><see cref="IHttpRequest.HttpVersion"/> cannot be empty.</exception>
|
||||
public HttpResponse(IHttpClientContext context, IHttpRequest request)
|
||||
{
|
||||
Check.Require(context, "context");
|
||||
Check.Require(request, "request");
|
||||
|
||||
_httpVersion = request.HttpVersion;
|
||||
if (string.IsNullOrEmpty(_httpVersion))
|
||||
_httpVersion = "HTTP/1.0";
|
||||
|
||||
Status = HttpStatusCode.OK;
|
||||
m_context = context;
|
||||
m_Connetion = request.Connection;
|
||||
requestID = request.ID;
|
||||
RawBufferStart = -1;
|
||||
RawBufferLen = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IHttpResponse"/> class.
|
||||
/// </summary>
|
||||
/// <param name="context">Client that send the <see cref="IHttpRequest"/>.</param>
|
||||
/// <param name="httpVersion">Version of HTTP protocol that the client uses.</param>
|
||||
/// <param name="connectionType">Type of HTTP connection used.</param>
|
||||
internal HttpResponse(IHttpClientContext context, string httpVersion, ConnectionType connectionType)
|
||||
{
|
||||
Check.NotEmpty(httpVersion, "httpVersion");
|
||||
|
||||
Status = HttpStatusCode.OK;
|
||||
m_context = context;
|
||||
_httpVersion = httpVersion;
|
||||
m_Connetion = connectionType;
|
||||
}
|
||||
private ConnectionType m_Connetion;
|
||||
public ConnectionType Connection
|
||||
{
|
||||
get { return m_Connetion; }
|
||||
set { return; }
|
||||
}
|
||||
|
||||
private int m_priority = 0;
|
||||
public int Priority
|
||||
{
|
||||
get { return m_priority;}
|
||||
set { m_priority = (value > 0 && m_priority < 3)? value : 0;}
|
||||
}
|
||||
|
||||
#region IHttpResponse Members
|
||||
|
||||
/// <summary>
|
||||
/// The body stream is used to cache the body contents
|
||||
/// before sending everything to the client. It's the simplest
|
||||
/// way to serve documents.
|
||||
/// </summary>
|
||||
public Stream Body
|
||||
{
|
||||
get
|
||||
{
|
||||
if(_body == null)
|
||||
_body = new MemoryStream();
|
||||
return _body;
|
||||
}
|
||||
set { _body = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The chunked encoding modifies the body of a message in order to
|
||||
/// transfer it as a series of chunks, each with its own size indicator,
|
||||
/// followed by an OPTIONAL trailer containing entity-header fields. This
|
||||
/// allows dynamically produced content to be transferred along with the
|
||||
/// information necessary for the recipient to verify that it has
|
||||
/// received the full message.
|
||||
/// </summary>
|
||||
public bool Chunked { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines the version of the HTTP Response for applications where it's required
|
||||
/// for this to be forced.
|
||||
/// </summary>
|
||||
public string ProtocolVersion
|
||||
{
|
||||
get { return _httpVersion; }
|
||||
set { _httpVersion = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encoding to use when sending stuff to the client.
|
||||
/// </summary>
|
||||
/// <remarks>Default is UTF8</remarks>
|
||||
public Encoding Encoding
|
||||
{
|
||||
get { return _encoding; }
|
||||
set { _encoding = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Number of seconds to keep connection alive
|
||||
/// </summary>
|
||||
/// <remarks>Only used if Connection property is set to <see cref="ConnectionType.KeepAlive"/>.</remarks>
|
||||
public int KeepAlive
|
||||
{
|
||||
get { return _keepAlive; }
|
||||
set
|
||||
{
|
||||
if (value > 400)
|
||||
_keepAlive = 400;
|
||||
else if (value <= 0)
|
||||
_keepAlive = 0;
|
||||
else
|
||||
_keepAlive = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status code that is sent to the client.
|
||||
/// </summary>
|
||||
/// <remarks>Default is <see cref="HttpStatusCode.OK"/></remarks>
|
||||
public HttpStatusCode Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about why a specific status code was used.
|
||||
/// </summary>
|
||||
public string Reason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the body. MUST be specified before sending the header,
|
||||
/// unless property Chunked is set to true.
|
||||
/// </summary>
|
||||
public long ContentLength
|
||||
{
|
||||
get { return _contentLength; }
|
||||
set { _contentLength = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kind of content in the body
|
||||
/// </summary>
|
||||
/// <remarks>Default type is "text/html"</remarks>
|
||||
public string ContentType
|
||||
{
|
||||
get { return _contentType; }
|
||||
set { _contentType = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Headers have been sent to the client-
|
||||
/// </summary>
|
||||
/// <remarks>You can not send any additional headers if they have already been sent.</remarks>
|
||||
public bool HeadersSent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The whole response have been sent.
|
||||
/// </summary>
|
||||
public bool Sent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cookies that should be created/changed.
|
||||
/// </summary>
|
||||
public ResponseCookies Cookies
|
||||
{
|
||||
get { return _cookies; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add another header to the document.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the header, case sensitive, use lower cases.</param>
|
||||
/// <param name="value">Header values can span over multiple lines as long as each line starts with a white space. New line chars should be \r\n</param>
|
||||
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
|
||||
/// <exception cref="ArgumentException">If value conditions have not been met.</exception>
|
||||
/// <remarks>Adding any header will override the default ones and those specified by properties.</remarks>
|
||||
public void AddHeader(string name, string value)
|
||||
{
|
||||
if (HeadersSent)
|
||||
throw new InvalidOperationException("Headers have already been sent.");
|
||||
|
||||
for (int i = 1; i < value.Length; ++i)
|
||||
{
|
||||
if (value[i] == '\r' && !char.IsWhiteSpace(value[i - 1]))
|
||||
throw new ArgumentException("New line in value do not start with a white space.");
|
||||
if (value[i] == '\n' && value[i - 1] != '\r')
|
||||
throw new ArgumentException("Invalid new line sequence, should be \\r\\n (crlf).");
|
||||
}
|
||||
|
||||
m_headers[name] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send headers and body to the browser.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If content have already been sent.</exception>
|
||||
public void SendOri()
|
||||
{
|
||||
if (Sent)
|
||||
throw new InvalidOperationException("Everything have already been sent.");
|
||||
|
||||
m_context.ReqResponseAboutToSend(requestID);
|
||||
if (m_context.MAXRequests == 0 || _keepAlive == 0)
|
||||
{
|
||||
Connection = ConnectionType.Close;
|
||||
m_context.TimeoutKeepAlive = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_keepAlive > 0)
|
||||
m_context.TimeoutKeepAlive = _keepAlive * 1000;
|
||||
}
|
||||
|
||||
if (!HeadersSent)
|
||||
{
|
||||
if (!SendHeaders())
|
||||
{
|
||||
_body.Dispose();
|
||||
Sent = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(RawBuffer != null)
|
||||
{
|
||||
if(RawBufferStart >= 0 && RawBufferLen > 0)
|
||||
{
|
||||
if (RawBufferStart > RawBuffer.Length)
|
||||
RawBufferStart = 0;
|
||||
|
||||
if (RawBufferLen + RawBufferStart > RawBuffer.Length)
|
||||
RawBufferLen = RawBuffer.Length - RawBufferStart;
|
||||
|
||||
/*
|
||||
int curlen;
|
||||
while(RawBufferLen > 0)
|
||||
{
|
||||
curlen = RawBufferLen;
|
||||
if(curlen > 8192)
|
||||
curlen = 8192;
|
||||
if (!_context.Send(RawBuffer, RawBufferStart, curlen))
|
||||
{
|
||||
RawBuffer = null;
|
||||
RawBufferStart = -1;
|
||||
RawBufferLen = -1;
|
||||
Body.Dispose();
|
||||
return;
|
||||
}
|
||||
RawBufferLen -= curlen;
|
||||
RawBufferStart += curlen;
|
||||
}
|
||||
*/
|
||||
if(RawBufferLen > 0)
|
||||
{
|
||||
if (!m_context.Send(RawBuffer, RawBufferStart, RawBufferLen))
|
||||
{
|
||||
RawBuffer = null;
|
||||
RawBufferStart = -1;
|
||||
RawBufferLen = -1;
|
||||
if(_body != null)
|
||||
_body.Dispose();
|
||||
Sent = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RawBuffer = null;
|
||||
RawBufferStart = -1;
|
||||
RawBufferLen = -1;
|
||||
}
|
||||
|
||||
if(_body != null && _body.Length > 0)
|
||||
{
|
||||
_body.Flush();
|
||||
_body.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var buffer = new byte[8192];
|
||||
int bytesRead = _body.Read(buffer, 0, 8192);
|
||||
while (bytesRead > 0)
|
||||
{
|
||||
if (!m_context.Send(buffer, 0, bytesRead))
|
||||
break;
|
||||
bytesRead = _body.Read(buffer, 0, 8192);
|
||||
}
|
||||
|
||||
_body.Dispose();
|
||||
}
|
||||
Sent = true;
|
||||
m_context.ReqResponseSent(requestID, Connection);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Make sure that you have specified <see cref="ContentLength"/> and sent the headers first.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
|
||||
/// <see cref="SendHeaders"/>
|
||||
/// <param name="offset">offset of first byte to send</param>
|
||||
/// <param name="count">number of bytes to send.</param>
|
||||
/// <seealso cref="Send"/>
|
||||
/// <seealso cref="SendHeaders"/>
|
||||
/// <remarks>This method can be used if you want to send body contents without caching them first. This
|
||||
/// is recommended for larger files to keep the memory usage low.</remarks>
|
||||
public bool SendBody(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (!HeadersSent)
|
||||
throw new InvalidOperationException("Send headers, and remember to specify ContentLength first.");
|
||||
|
||||
bool sent = m_context.Send(buffer, offset, count);
|
||||
Sent = true;
|
||||
if (sent)
|
||||
m_context.ReqResponseSent(requestID, Connection);
|
||||
return sent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure that you have specified <see cref="ContentLength"/> and sent the headers first.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
|
||||
/// <see cref="SendHeaders"/>
|
||||
/// <seealso cref="Send"/>
|
||||
/// <seealso cref="SendHeaders"/>
|
||||
/// <remarks>This method can be used if you want to send body contents without caching them first. This
|
||||
/// is recommended for larger files to keep the memory usage low.</remarks>
|
||||
public bool SendBody(byte[] buffer)
|
||||
{
|
||||
if (!HeadersSent)
|
||||
throw new InvalidOperationException("Send headers, and remember to specify ContentLength first.");
|
||||
|
||||
bool sent = m_context.Send(buffer);
|
||||
if (sent)
|
||||
m_context.ReqResponseSent(requestID, Connection);
|
||||
Sent = true;
|
||||
return sent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send headers to the client.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
|
||||
/// <seealso cref="AddHeader"/>
|
||||
/// <seealso cref="Send"/>
|
||||
/// <seealso cref="SendBody(byte[])"/>
|
||||
public bool SendHeaders()
|
||||
{
|
||||
if (HeadersSent)
|
||||
throw new InvalidOperationException("Header have already been sent.");
|
||||
|
||||
HeadersSent = true;
|
||||
|
||||
if (m_headers["Date"] == null)
|
||||
m_headers["Date"] = DateTime.Now.ToString("r");
|
||||
if (m_headers["Content-Length"] == null)
|
||||
{
|
||||
int len = (int)_contentLength;
|
||||
if(len == 0)
|
||||
{
|
||||
if(_body != null)
|
||||
len = (int)_body.Length;
|
||||
if(RawBuffer != null)
|
||||
len += RawBufferLen;
|
||||
}
|
||||
m_headers["Content-Length"] = len.ToString();
|
||||
}
|
||||
if (m_headers["Content-Type"] == null)
|
||||
m_headers["Content-Type"] = _contentType ?? DefaultContentType;
|
||||
if (m_headers["Server"] == null)
|
||||
m_headers["Server"] = "Tiny WebServer";
|
||||
|
||||
int keepaliveS = m_context.TimeoutKeepAlive / 1000;
|
||||
if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && m_context.MAXRequests > 0)
|
||||
{
|
||||
m_headers["Keep-Alive"] = "timeout=" + keepaliveS + ", max=" + m_context.MAXRequests;
|
||||
m_headers["Connection"] = "Keep-Alive";
|
||||
}
|
||||
else
|
||||
m_headers["Connection"] = "close";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat("{0} {1} {2}\r\n", _httpVersion, (int)Status,
|
||||
string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
|
||||
|
||||
for (int i = 0; i < m_headers.Count; ++i)
|
||||
{
|
||||
string headerName = m_headers.AllKeys[i];
|
||||
string[] values = m_headers.GetValues(i);
|
||||
if (values == null) continue;
|
||||
foreach (string value in values)
|
||||
sb.AppendFormat("{0}: {1}\r\n", headerName, value);
|
||||
}
|
||||
|
||||
foreach (ResponseCookie cookie in Cookies)
|
||||
sb.AppendFormat("Set-Cookie: {0}\r\n", cookie);
|
||||
|
||||
sb.Append("\r\n");
|
||||
|
||||
m_headers.Clear();
|
||||
|
||||
return m_context.Send(Encoding.GetBytes(sb.ToString()));
|
||||
}
|
||||
|
||||
public byte[] GetHeaders()
|
||||
{
|
||||
HeadersSent = true;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
if(string.IsNullOrWhiteSpace(_httpVersion))
|
||||
sb.AppendFormat("HTTP1/0 {0} {1}\r\n", (int)Status,
|
||||
string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
|
||||
else
|
||||
sb.AppendFormat("{0} {1} {2}\r\n", _httpVersion, (int)Status,
|
||||
string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
|
||||
|
||||
if (m_headers["Date"] == null)
|
||||
sb.AppendFormat("Date: {0}\r\n", DateTime.Now.ToString("r"));
|
||||
if (m_headers["Content-Length"] == null)
|
||||
{
|
||||
long len = _contentLength;
|
||||
if (len == 0)
|
||||
{
|
||||
len = Body.Length;
|
||||
if (RawBuffer != null && RawBufferLen > 0)
|
||||
len += RawBufferLen;
|
||||
}
|
||||
sb.AppendFormat("Content-Length: {0}\r\n", len);
|
||||
}
|
||||
if (m_headers["Content-Type"] == null)
|
||||
sb.AppendFormat("Content-Type: {0}\r\n", _contentType ?? DefaultContentType);
|
||||
if (m_headers["Server"] == null)
|
||||
sb.Append("Server: OSWebServer\r\n");
|
||||
|
||||
int keepaliveS = m_context.TimeoutKeepAlive / 1000;
|
||||
if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && m_context.MAXRequests > 0)
|
||||
{
|
||||
sb.AppendFormat("Keep-Alive:timeout={0}, max={1}\r\n", keepaliveS, m_context.MAXRequests);
|
||||
sb.Append("Connection: Keep-Alive\r\n");
|
||||
}
|
||||
else
|
||||
sb.Append("Connection: close\r\n");
|
||||
|
||||
if (m_headers["Connection"] != null)
|
||||
m_headers["Connection"] = null;
|
||||
if (m_headers["Keep-Alive"] != null)
|
||||
m_headers["Keep-Alive"] = null;
|
||||
|
||||
for (int i = 0; i < m_headers.Count; ++i)
|
||||
{
|
||||
string headerName = m_headers.AllKeys[i];
|
||||
string[] values = m_headers.GetValues(i);
|
||||
if (values == null) continue;
|
||||
foreach (string value in values)
|
||||
sb.AppendFormat("{0}: {1}\r\n", headerName, value);
|
||||
}
|
||||
|
||||
foreach (ResponseCookie cookie in Cookies)
|
||||
sb.AppendFormat("Set-Cookie: {0}\r\n", cookie);
|
||||
|
||||
sb.Append("\r\n");
|
||||
|
||||
m_headers.Clear();
|
||||
|
||||
return Encoding.GetBytes(sb.ToString());
|
||||
}
|
||||
|
||||
public void Send()
|
||||
{
|
||||
if (Sent)
|
||||
throw new InvalidOperationException("Everything have already been sent.");
|
||||
|
||||
if (m_context.MAXRequests == 0 || _keepAlive == 0)
|
||||
{
|
||||
Connection = ConnectionType.Close;
|
||||
m_context.TimeoutKeepAlive = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_keepAlive > 0)
|
||||
m_context.TimeoutKeepAlive = _keepAlive * 1000;
|
||||
}
|
||||
|
||||
m_headerBytes = GetHeaders();
|
||||
if (RawBuffer != null)
|
||||
{
|
||||
if (RawBufferStart < 0 || RawBufferStart > RawBuffer.Length)
|
||||
return;
|
||||
|
||||
if (RawBufferLen < 0)
|
||||
RawBufferLen = RawBuffer.Length;
|
||||
|
||||
if (RawBufferLen + RawBufferStart > RawBuffer.Length)
|
||||
RawBufferLen = RawBuffer.Length - RawBufferStart;
|
||||
|
||||
int tlen = m_headerBytes.Length + RawBufferLen;
|
||||
if(RawBufferLen > 0 && tlen < 16384)
|
||||
{
|
||||
byte[] tmp = new byte[tlen];
|
||||
Array.Copy(m_headerBytes, tmp, m_headerBytes.Length);
|
||||
Array.Copy(RawBuffer, RawBufferStart, tmp, m_headerBytes.Length, RawBufferLen);
|
||||
m_headerBytes = null;
|
||||
RawBuffer = tmp;
|
||||
RawBufferStart = 0;
|
||||
RawBufferLen = tlen;
|
||||
}
|
||||
}
|
||||
m_context.StartSendResponse(this);
|
||||
}
|
||||
|
||||
public async Task SendNextAsync(int bytesLimit)
|
||||
{
|
||||
if (m_headerBytes != null)
|
||||
{
|
||||
if(!await m_context.SendAsync(m_headerBytes, 0, m_headerBytes.Length).ConfigureAwait(false))
|
||||
{
|
||||
if(_body != null)
|
||||
_body.Dispose();
|
||||
RawBuffer = null;
|
||||
Sent = true;
|
||||
return;
|
||||
}
|
||||
bytesLimit -= m_headerBytes.Length;
|
||||
m_headerBytes = null;
|
||||
if(bytesLimit <= 0)
|
||||
{
|
||||
m_context.ContinueSendResponse();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (RawBuffer != null)
|
||||
{
|
||||
if (RawBufferLen > 0)
|
||||
{
|
||||
bool sendRes;
|
||||
if(RawBufferLen > bytesLimit)
|
||||
{
|
||||
sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, bytesLimit).ConfigureAwait(false);
|
||||
RawBufferLen -= bytesLimit;
|
||||
RawBufferStart += bytesLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, RawBufferLen).ConfigureAwait(false);
|
||||
RawBufferLen = 0;
|
||||
}
|
||||
|
||||
if (!sendRes)
|
||||
{
|
||||
RawBuffer = null;
|
||||
if(_body != null)
|
||||
Body.Dispose();
|
||||
Sent = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (RawBufferLen <= 0)
|
||||
RawBuffer = null;
|
||||
else
|
||||
{
|
||||
m_context.ContinueSendResponse();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_body != null && _body.Length != 0)
|
||||
{
|
||||
_body.Flush();
|
||||
_body.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
RawBuffer = new byte[_body.Length];
|
||||
RawBufferLen = _body.Read(RawBuffer, 0, (int)_body.Length);
|
||||
_body.Dispose();
|
||||
|
||||
if(RawBufferLen > 0)
|
||||
{
|
||||
bool sendRes;
|
||||
if (RawBufferLen > bytesLimit)
|
||||
{
|
||||
sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, bytesLimit).ConfigureAwait(false);
|
||||
RawBufferLen -= bytesLimit;
|
||||
RawBufferStart += bytesLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, RawBufferLen).ConfigureAwait(false);
|
||||
RawBufferLen = 0;
|
||||
}
|
||||
|
||||
if (!sendRes)
|
||||
{
|
||||
RawBuffer = null;
|
||||
Sent = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (RawBufferLen > 0)
|
||||
{
|
||||
m_context.ContinueSendResponse();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_body != null)
|
||||
_body.Dispose();
|
||||
Sent = true;
|
||||
m_context.ReqResponseSent(requestID, Connection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redirect client to somewhere else using the 302 status code.
|
||||
/// </summary>
|
||||
/// <param name="uri">Destination of the redirect</param>
|
||||
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
|
||||
/// <remarks>You can not do anything more with the request when a redirect have been done. This should be your last
|
||||
/// action.</remarks>
|
||||
public void Redirect(Uri uri)
|
||||
{
|
||||
Status = HttpStatusCode.Redirect;
|
||||
m_headers["location"] = uri.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// redirect to somewhere
|
||||
/// </summary>
|
||||
/// <param name="url">where the redirect should go</param>
|
||||
/// <remarks>
|
||||
/// No body are allowed when doing redirects.
|
||||
/// </remarks>
|
||||
public void Redirect(string url)
|
||||
{
|
||||
Status = HttpStatusCode.Redirect;
|
||||
m_headers["location"] = url;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if(Body != null && Body.CanRead)
|
||||
Body.Dispose();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,375 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Response that is sent back to the web browser / client.
|
||||
///
|
||||
/// A response can be sent if different ways. The easiest one is
|
||||
/// to just fill the Body stream with content, everything else
|
||||
/// will then be taken care of by the framework. The default content-type
|
||||
/// is text/html, you should change it if you send anything else.
|
||||
///
|
||||
/// The second and slighty more complex way is to send the response
|
||||
/// as parts. Start with sending the header using the SendHeaders method and
|
||||
/// then you can send the body using SendBody method, but do not forget
|
||||
/// to set ContentType and ContentLength before doing so.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// public void MyHandler(HttpRequest request, HttpResponse response)
|
||||
/// {
|
||||
///
|
||||
/// }
|
||||
/// </example>
|
||||
/// todo: add two examples, using SendHeaders/SendBody and just the Body stream.
|
||||
public class HttpResponse
|
||||
{
|
||||
private Stream _body = new MemoryStream();
|
||||
private bool _chunked;
|
||||
private ConnectionType _connection;
|
||||
private readonly HttpClientContext _context;
|
||||
private Encoding _encoding = Encoding.UTF8;
|
||||
private readonly NameValueCollection _headers = new NameValueCollection();
|
||||
private bool _headersSent;
|
||||
private bool _sent;
|
||||
private readonly string _httpVersion;
|
||||
private int _keepAlive = 20;
|
||||
private string _reason;
|
||||
private HttpStatusCode _status;
|
||||
private long _contentLength;
|
||||
private string _contentType;
|
||||
private bool _contentTypeChangedByCode;
|
||||
private readonly ResponseCookies _cookies = new ResponseCookies();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpResponse"/> class.
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
/// <param name="request">The request.</param>
|
||||
public HttpResponse(HttpClientContext context, HttpRequest request)
|
||||
{
|
||||
_httpVersion = request.HttpVersion;
|
||||
if (string.IsNullOrEmpty(_httpVersion))
|
||||
throw new ArgumentException("HttpVersion in HttpRequest cannot be empty.");
|
||||
|
||||
Status = HttpStatusCode.OK;
|
||||
_context = context;
|
||||
Connection = request.Connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The body stream is used to cache the body contents
|
||||
/// before sending everything to the client. It's the simplest
|
||||
/// way to serve documents.
|
||||
/// </summary>
|
||||
public Stream Body
|
||||
{
|
||||
get { return _body; }
|
||||
set { _body = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The chunked encoding modifies the body of a message in order to
|
||||
/// transfer it as a series of chunks, each with its own size indicator,
|
||||
/// followed by an OPTIONAL trailer containing entity-header fields. This
|
||||
/// allows dynamically produced content to be transferred along with the
|
||||
/// information necessary for the recipient to verify that it has
|
||||
/// received the full message.
|
||||
/// </summary>
|
||||
public bool Chunked
|
||||
{
|
||||
get { return _chunked; }
|
||||
set { _chunked = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kind of connection
|
||||
/// </summary>
|
||||
public ConnectionType Connection
|
||||
{
|
||||
get { return _connection; }
|
||||
set { _connection = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encoding to use when sending stuff to the client.
|
||||
/// </summary>
|
||||
/// <remarks>Default is UTF8</remarks>
|
||||
public Encoding Encoding
|
||||
{
|
||||
get { return _encoding; }
|
||||
set { _encoding = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of seconds to keep connection alive
|
||||
/// </summary>
|
||||
/// <remarks>Only used if Connection property is set to ConnectionType.KeepAlive</remarks>
|
||||
public int KeepAlive
|
||||
{
|
||||
get { return _keepAlive; }
|
||||
set { _keepAlive = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status code that is sent to the client.
|
||||
/// </summary>
|
||||
/// <remarks>Default is HttpStatusCode.Ok</remarks>
|
||||
public HttpStatusCode Status
|
||||
{
|
||||
get { return _status; }
|
||||
set { _status = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about why a specific status code was used.
|
||||
/// </summary>
|
||||
public string Reason
|
||||
{
|
||||
get { return _reason; }
|
||||
set { _reason = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Size of the body. MUST be specified before sending the header,
|
||||
/// unless property Chunked is set to true.
|
||||
/// </summary>
|
||||
public long ContentLength
|
||||
{
|
||||
get { return _contentLength; }
|
||||
set { _contentLength = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kind of content in the body
|
||||
/// </summary>
|
||||
/// <remarks>Default is text/html</remarks>
|
||||
public string ContentType
|
||||
{
|
||||
get { return _contentType; }
|
||||
set
|
||||
{
|
||||
_contentType = value;
|
||||
_contentTypeChangedByCode = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Headers have been sent to the client-
|
||||
/// </summary>
|
||||
/// <remarks>You can not send any additional headers if they have already been sent.</remarks>
|
||||
public bool HeadersSent
|
||||
{
|
||||
get { return _headersSent; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The whole response have been sent.
|
||||
/// </summary>
|
||||
public bool Sent
|
||||
{
|
||||
get { return _sent; }
|
||||
}
|
||||
|
||||
internal bool ContentTypeChangedByCode
|
||||
{
|
||||
get { return _contentTypeChangedByCode; }
|
||||
set { _contentTypeChangedByCode = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cookies that should be created/changed.
|
||||
/// </summary>
|
||||
public ResponseCookies Cookies
|
||||
{
|
||||
get { return _cookies; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add another header to the document.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the header, case sensitive, use lower cases.</param>
|
||||
/// <param name="value">Header values can span over multiple lines as long as each line starts with a white space. New line chars should be \r\n</param>
|
||||
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
|
||||
/// <exception cref="ArgumentException">If value conditions have not been met.</exception>
|
||||
/// <remarks>Adding any header will override the default ones and those specified by properties.</remarks>
|
||||
public void AddHeader(string name, string value)
|
||||
{
|
||||
if (HeadersSent)
|
||||
throw new InvalidOperationException("Headers have already been sent.");
|
||||
|
||||
for (int i = 1; i < value.Length; ++i )
|
||||
{
|
||||
if (value[i] == '\r' && !char.IsWhiteSpace(value[i-1]))
|
||||
throw new ArgumentException("New line in value do not start with a white space.");
|
||||
if (value[i] == '\n' && value[i-1] != '\r')
|
||||
throw new ArgumentException("Invalid new line sequence, should be \\r\\n (crlf).");
|
||||
}
|
||||
_headers[name.ToLower()] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send headers and body to the browser.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If content have already been sent.</exception>
|
||||
public void Send()
|
||||
{
|
||||
if (!_headersSent)
|
||||
SendHeaders();
|
||||
if (_sent)
|
||||
throw new InvalidOperationException("Everyting have already been sent.");
|
||||
if (Body.Length == 0)
|
||||
{
|
||||
if (Connection == ConnectionType.Close)
|
||||
_context.Disconnect(SocketError.Success);
|
||||
return;
|
||||
}
|
||||
|
||||
Body.Flush();
|
||||
Body.Seek(0, SeekOrigin.Begin);
|
||||
byte[] buffer = new byte[4196];
|
||||
int bytesRead = Body.Read(buffer, 0, 4196);
|
||||
while (bytesRead > 0)
|
||||
{
|
||||
_context.Send(buffer, 0, bytesRead);
|
||||
bytesRead = Body.Read(buffer, 0, 4196);
|
||||
}
|
||||
|
||||
if (Connection == ConnectionType.Close)
|
||||
_context.Disconnect(SocketError.Success);
|
||||
|
||||
_sent = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure that you have specified ContentLength and sent the headers first.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
|
||||
/// <see cref="SendHeaders"/>
|
||||
/// <param name="offset">offest of first byte to send</param>
|
||||
/// <param name="count">number of bytes to send.</param>
|
||||
/// <seealso cref="Send"/>
|
||||
/// <seealso cref="SendHeaders"/>
|
||||
/// <remarks>This method can be used if you want to send body contents without caching them first. This
|
||||
/// is recommended for larger files to keep the memory usage low.</remarks>
|
||||
public void SendBody(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (!HeadersSent)
|
||||
throw new InvalidOperationException("Send headers, and remember to specify ContentLength first.");
|
||||
|
||||
_sent = true;
|
||||
_context.Send(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure that you have specified ContentLength and sent the headers first.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
|
||||
/// <see cref="SendHeaders"/>
|
||||
/// <seealso cref="Send"/>
|
||||
/// <seealso cref="SendHeaders"/>
|
||||
/// <remarks>This method can be used if you want to send body contents without caching them first. This
|
||||
/// is recommended for larger files to keep the memory usage low.</remarks>
|
||||
public void SendBody(byte[] buffer)
|
||||
{
|
||||
if (!HeadersSent)
|
||||
throw new InvalidOperationException("Send headers, and remember to specify ContentLength first.");
|
||||
|
||||
_sent = true;
|
||||
_context.Send(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send headers to the client.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
|
||||
/// <seealso cref="AddHeader"/>
|
||||
/// <seealso cref="Send"/>
|
||||
/// <seealso cref="SendBody(byte[])"/>
|
||||
public void SendHeaders()
|
||||
{
|
||||
if (HeadersSent)
|
||||
throw new InvalidOperationException("Header have already been sent.");
|
||||
|
||||
_headersSent = true;
|
||||
|
||||
if (_headers["date"] == null)
|
||||
_headers["date"] = DateTime.Now.ToString("r");
|
||||
if (_headers["content-length"] == null)
|
||||
_headers["content-length"] = _contentLength == 0 ? Body.Length.ToString() : _contentLength.ToString();
|
||||
if (_headers["content-type"] == null)
|
||||
_headers["content-type"] = _contentType;
|
||||
if (_headers["server"] == null)
|
||||
_headers["server"] = "Tiny WebServer";
|
||||
|
||||
if (Connection == ConnectionType.KeepAlive)
|
||||
{
|
||||
_headers["keep-alive"] = "timeout=" + _keepAlive + ", max=" + _keepAlive*20;
|
||||
_headers["connection"] = "keep-alive";
|
||||
}
|
||||
else
|
||||
_headers["connection"] = "close";
|
||||
|
||||
if (Body.Length != 0)
|
||||
{
|
||||
if (_headers["content-type"] == null)
|
||||
_headers["content-type"] = _contentType ?? "text/html;charset=UTF-8";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendFormat("{0} {1} {2}\r\n", _httpVersion, (int) Status,
|
||||
string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
|
||||
|
||||
for (int i = 0; i < _headers.Count; ++i)
|
||||
sb.AppendFormat("{0}: {1}\r\n", _headers.AllKeys[i], _headers[i]);
|
||||
|
||||
foreach (ResponseCookie cookie in Cookies)
|
||||
sb.AppendFormat("Set-Cookie: {0}\r\n", cookie);
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
_context.Send(Encoding.GetBytes(sb.ToString()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redirect client to somewhere else using the 302 status code.
|
||||
/// </summary>
|
||||
/// <param name="uri">Destination of the redirect</param>
|
||||
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
|
||||
/// <remarks>You can not do anything more with the request when a redirect have been done. This should be your last
|
||||
/// action.</remarks>
|
||||
public void Redirect(Uri uri)
|
||||
{
|
||||
Status = HttpStatusCode.Redirect;
|
||||
_headers["location"] = uri.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// redirect to somewhere
|
||||
/// </summary>
|
||||
/// <param name="url">where the redirect should go</param>
|
||||
/// <remarks>
|
||||
/// No body are allowed when doing redirects.
|
||||
/// </remarks>
|
||||
public void Redirect(string url)
|
||||
{
|
||||
Status = HttpStatusCode.Redirect;
|
||||
_headers["location"] = url;
|
||||
}
|
||||
|
||||
/*
|
||||
* HTTP/1.1 200 OK
|
||||
Date: Sun, 16 Mar 2008 08:01:36 GMT
|
||||
Server: Apache/2.2.6 (Win32) PHP/5.2.4
|
||||
Content-Length: 685
|
||||
Connection: close
|
||||
Content-Type: text/html;charset=UTF-8*/
|
||||
}
|
||||
}
|
||||
@@ -1,824 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using HttpServer.Authentication;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.FormDecoders;
|
||||
using HttpServer.HttpModules;
|
||||
using HttpServer.Rules;
|
||||
using HttpServer.Sessions;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate used to find a realm/domain.
|
||||
/// </summary>
|
||||
/// <param name="domain"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Realms are used during HTTP Authentication
|
||||
/// </remarks>
|
||||
/// <seealso cref="AuthenticationModule"/>
|
||||
/// <seealso cref="AuthenticationHandler"/>
|
||||
public delegate string RealmHandler(string domain);
|
||||
|
||||
/// <summary>
|
||||
/// A complete HTTP server, you need to add a module to it to be able to handle incoming requests.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // this small example will add two web site modules, thus handling
|
||||
/// // two different sites. In reality you should add Controller modules or something
|
||||
/// // two the website modules to be able to handle different requests.
|
||||
/// HttpServer server = new HttpServer();
|
||||
/// server.Add(new WebSiteModule("www.gauffin.com", "Gauffin Telecom AB"));
|
||||
/// server.Add(new WebSiteModule("www.vapadi.se", "Remote PBX"));
|
||||
///
|
||||
/// // start regular http
|
||||
/// server.Start(IPAddress.Any, 80);
|
||||
///
|
||||
/// // start https
|
||||
/// server.Start(IPAddress.Any, 443, myCertificate);
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <seealso cref="HttpModule"/>
|
||||
/// <seealso cref="FileModule"/>
|
||||
/// <seealso cref="HttpListener"/>
|
||||
public class HttpServer
|
||||
{
|
||||
private FormDecoderProvider _formDecodersProvider;
|
||||
private readonly List<HttpModule> _modules = new List<HttpModule>();
|
||||
private readonly List<IRule> _rules = new List<IRule>();
|
||||
private readonly List<AuthenticationModule> _authModules = new List<AuthenticationModule>();
|
||||
private HttpListener _httpListener;
|
||||
private HttpListener _httpsListener;
|
||||
private string _serverName = "SeeSharpWebServer";
|
||||
private string _sessionCookieName = "__tiny_sessid";
|
||||
private IHttpSessionStore _sessionStore;
|
||||
private ILogWriter _logWriter = NullLogWriter.Instance;
|
||||
private int _backLog = 10;
|
||||
private ExceptionHandler _exceptionHandler;
|
||||
private readonly IComponentProvider _components;
|
||||
private RequestQueue _requestQueue;
|
||||
[ThreadStatic]
|
||||
private static HttpServer _current;
|
||||
|
||||
/// <summary>
|
||||
/// Server that is handling the current request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Will be set as soon as a request arrives to the <see cref="HttpServer"/> object.
|
||||
/// </remarks>
|
||||
public static HttpServer Current
|
||||
{
|
||||
get { return _current; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpServer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="provider">Used to get all components used in the server..</param>
|
||||
public HttpServer(IComponentProvider provider)
|
||||
{
|
||||
_components = provider;
|
||||
_requestQueue = new RequestQueue(ProcessRequestWrapper);
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpServer"/> class.
|
||||
/// </summary>
|
||||
public HttpServer()
|
||||
{
|
||||
_components = new ComponentProvider();
|
||||
_requestQueue = new RequestQueue(ProcessRequestWrapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpServer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="decoderProvider">Form decoders are used to convert different types of posted data to the <see cref="HttpInput"/> object types.</param>
|
||||
/// <seealso cref="IFormDecoder"/>
|
||||
/// <seealso cref="FormDecoderProviders"/>
|
||||
public HttpServer(FormDecoderProvider decoderProvider)
|
||||
{
|
||||
Check.Require(decoderProvider, "decoderProvider");
|
||||
_components = new ComponentProvider();
|
||||
_components.AddInstance<FormDecoderProvider>(decoderProvider);
|
||||
_requestQueue = new RequestQueue(ProcessRequestWrapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpServer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="sessionStore">A session store is used to save and retrieve sessions</param>
|
||||
/// <seealso cref="IHttpSessionStore"/>
|
||||
public HttpServer(IHttpSessionStore sessionStore)
|
||||
{
|
||||
Check.Require(sessionStore, "sessionStore");
|
||||
_components = new ComponentProvider();
|
||||
_components.AddInstance<IHttpSessionStore>(sessionStore);
|
||||
_requestQueue = new RequestQueue(ProcessRequestWrapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpServer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logWriter">The log writer.</param>
|
||||
/// <seealso cref="LogWriter"/>
|
||||
public HttpServer(ILogWriter logWriter)
|
||||
{
|
||||
Check.Require(logWriter, "logWriter");
|
||||
_components = new ComponentProvider();
|
||||
_components.AddInstance<ILogWriter>(logWriter);
|
||||
_requestQueue = new RequestQueue(ProcessRequestWrapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpServer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="decoderProvider">Form decoders are used to convert different types of posted data to the <see cref="HttpInput"/> object types.</param>
|
||||
/// <param name="logWriter">The log writer.</param>
|
||||
/// <seealso cref="IFormDecoder"/>
|
||||
/// <seealso cref="FormDecoderProviders"/>
|
||||
/// <seealso cref="LogWriter"/>
|
||||
public HttpServer(FormDecoderProvider decoderProvider, ILogWriter logWriter)
|
||||
{
|
||||
Check.Require(decoderProvider, "decoderProvider");
|
||||
Check.Require(logWriter, "logWriter");
|
||||
_components = new ComponentProvider();
|
||||
_components.AddInstance<FormDecoderProvider>(decoderProvider);
|
||||
_requestQueue = new RequestQueue(ProcessRequestWrapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpServer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="decoderProvider">Form decoders are used to convert different types of posted data to the <see cref="HttpInput"/> object types.</param>
|
||||
/// <param name="sessionStore">A session store is used to save and retrieve sessions</param>
|
||||
/// <param name="logWriter">The log writer.</param>
|
||||
/// <seealso cref="IFormDecoder"/>
|
||||
/// <seealso cref="FormDecoderProviders"/>
|
||||
/// <seealso cref="LogWriter"/>
|
||||
/// <seealso cref="IHttpSessionStore"/>
|
||||
public HttpServer(FormDecoderProvider decoderProvider, IHttpSessionStore sessionStore, ILogWriter logWriter)
|
||||
{
|
||||
Check.Require(decoderProvider, "decoderProvider");
|
||||
Check.Require(sessionStore, "sessionStore");
|
||||
_components = new ComponentProvider();
|
||||
_components.AddInstance<FormDecoderProvider>(sessionStore);
|
||||
_components.AddInstance<IHttpSessionStore>(sessionStore);
|
||||
if (logWriter != null)
|
||||
_components.AddInstance<ILogWriter>(logWriter);
|
||||
_requestQueue = new RequestQueue(ProcessRequestWrapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modules used for authentication. The module that is is added first is used as
|
||||
/// the default authentication module.
|
||||
/// </summary>
|
||||
/// <remarks>Use the corresponding property
|
||||
/// in the <see cref="WebSiteModule"/> if you are using multiple websites.</remarks>
|
||||
public IList<AuthenticationModule> AuthenticationModules
|
||||
{
|
||||
get { return _authModules; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Form decoder providers are used to decode request body (which normally contains form data).
|
||||
/// </summary>
|
||||
public FormDecoderProvider FormDecoderProviders
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_formDecodersProvider == null)
|
||||
_formDecodersProvider = _components.Get<FormDecoderProvider>() ?? new FormDecoderProvider();
|
||||
return _formDecodersProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server name sent in HTTP responses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Do NOT include version in name, since it makes it
|
||||
/// easier for hackers.
|
||||
/// </remarks>
|
||||
public string ServerName
|
||||
{
|
||||
get { return _serverName; }
|
||||
set { _serverName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of cookie where session id is stored.
|
||||
/// </summary>
|
||||
public string SessionCookieName
|
||||
{
|
||||
get { return _sessionCookieName; }
|
||||
set { _sessionCookieName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specified where logging should go.
|
||||
/// </summary>
|
||||
/// <seealso cref="NullLogWriter"/>
|
||||
/// <seealso cref="ConsoleLogWriter"/>
|
||||
/// <seealso cref="LogWriter"/>
|
||||
public ILogWriter LogWriter
|
||||
{
|
||||
get { return _logWriter; }
|
||||
set
|
||||
{
|
||||
_logWriter = value ?? NullLogWriter.Instance;
|
||||
foreach (HttpModule module in _modules)
|
||||
module.SetLogWriter(_logWriter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of connections that can wait to be accepted by the server.
|
||||
/// </summary>
|
||||
/// <remarks>Default is 10.</remarks>
|
||||
public int BackLog
|
||||
{
|
||||
get { return _backLog; }
|
||||
set { _backLog = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets maximum number of allowed simultaneous requests.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This property is useful in busy systems. The HTTP server
|
||||
/// will start queuing new requests if this limit is hit, instead
|
||||
/// of trying to process all incoming requests directly.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The default number if allowed simultaneous requests are 10.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int MaxRequestCount
|
||||
{
|
||||
get { return _requestQueue.MaxRequestCount; }
|
||||
set { _requestQueue.MaxRequestCount = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets maximum number of requests queuing to be handled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The WebServer will start turning requests away if response code
|
||||
/// <see cref="HttpStatusCode.ServiceUnavailable"/> to indicate that the server
|
||||
/// is too busy to be able to handle the request.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int MaxQueueSize
|
||||
{
|
||||
get { return _requestQueue.MaxQueueSize; }
|
||||
set { _requestQueue.MaxQueueSize = value; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified rule.
|
||||
/// </summary>
|
||||
/// <param name="rule">The rule.</param>
|
||||
public void Add(IRule rule)
|
||||
{
|
||||
_rules.Add(rule);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a <see cref="HttpModule"/> to the server.
|
||||
/// </summary>
|
||||
/// <param name="module">mode to add</param>
|
||||
public void Add(HttpModule module)
|
||||
{
|
||||
_modules.Add(module);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the request body.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <exception cref="InternalServerException">Failed to decode form data.</exception>
|
||||
protected virtual void DecodeBody(IHttpRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (request.Body.Length > 0)
|
||||
request.DecodeBody(_formDecodersProvider);
|
||||
}
|
||||
catch (InvalidOperationException err)
|
||||
{
|
||||
throw new InternalServerException("Failed to decode form data.", err);
|
||||
}
|
||||
catch (InvalidDataException err)
|
||||
{
|
||||
throw new InternalServerException("Form contains invalid format.", err);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a HTTP error page (that will be added to the response body).
|
||||
/// response status code is also set.
|
||||
/// </summary>
|
||||
/// <param name="response">Response that the page will be generated in.</param>
|
||||
/// <param name="error"><see cref="HttpStatusCode"/>.</param>
|
||||
/// <param name="body">response body contents.</param>
|
||||
protected virtual void ErrorPage(IHttpResponse response, HttpStatusCode error, string body)
|
||||
{
|
||||
response.Reason = "Internal server error";
|
||||
response.Status = error;
|
||||
response.ContentType = "text/plain";
|
||||
|
||||
StreamWriter writer = new StreamWriter(response.Body);
|
||||
writer.WriteLine(body);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a HTTP error page (that will be added to the response body).
|
||||
/// response status code is also set.
|
||||
/// </summary>
|
||||
/// <param name="response">Response that the page will be generated in.</param>
|
||||
/// <param name="err">exception.</param>
|
||||
protected virtual void ErrorPage(IHttpResponse response, HttpException err)
|
||||
{
|
||||
response.Reason = err.GetType().Name;
|
||||
response.Status = err.HttpStatusCode;
|
||||
response.ContentType = "text/plain";
|
||||
StreamWriter writer = new StreamWriter(response.Body);
|
||||
#if DEBUG
|
||||
writer.WriteLine(err);
|
||||
#else
|
||||
writer.WriteLine(err.Message);
|
||||
#endif
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Realms are used by the <see cref="AuthenticationModule"/>s.
|
||||
/// </summary>
|
||||
/// <param name="request">HTTP request</param>
|
||||
/// <returns>domain/realm.</returns>
|
||||
protected virtual string GetRealm(IHttpRequest request)
|
||||
{
|
||||
return RealmWanted != null ? RealmWanted(request.Headers["host"] ?? "localhost") : ServerName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process an incoming request.
|
||||
/// </summary>
|
||||
/// <param name="context">connection to client</param>
|
||||
/// <param name="request">request information</param>
|
||||
/// <param name="response">response that should be filled</param>
|
||||
/// <param name="session">session information</param>
|
||||
protected virtual void HandleRequest(IHttpClientContext context, IHttpRequest request, IHttpResponse response,
|
||||
IHttpSession session)
|
||||
{
|
||||
_logWriter.Write(this, LogPrio.Trace, "Processing request....");
|
||||
bool handled = false;
|
||||
try
|
||||
{
|
||||
DecodeBody(request);
|
||||
if (ProcessAuthentication(request, response, session))
|
||||
{
|
||||
foreach (HttpModule module in _modules)
|
||||
{
|
||||
if (!module.Process(request, response, session))
|
||||
continue;
|
||||
|
||||
handled = true;
|
||||
if (!module.AllowSecondaryProcessing)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (HttpException err)
|
||||
{
|
||||
if (err.HttpStatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
AuthenticationModule mod;
|
||||
lock (_authModules)
|
||||
mod = _authModules.Count > 0 ? _authModules[0] : null;
|
||||
|
||||
if (mod != null)
|
||||
RequestAuthentication(mod, request, response);
|
||||
}
|
||||
else
|
||||
ErrorPage(response, err);
|
||||
}
|
||||
|
||||
if (!handled && response.Status == HttpStatusCode.OK)
|
||||
ErrorPage(response, HttpStatusCode.NotFound, "Resource not found: " + request.Uri);
|
||||
|
||||
if (!response.HeadersSent)
|
||||
{
|
||||
// Dispose session if it was not used.
|
||||
if (session.Count > 0)
|
||||
{
|
||||
_sessionStore.Save(session);
|
||||
// only set session cookie if it have not been sent in the request.
|
||||
if (request.Cookies[_sessionCookieName] == null)
|
||||
response.Cookies.Add(new ResponseCookie(_sessionCookieName, session.Id, DateTime.MinValue));//DateTime.Now.AddMinutes(20).AddDays(1)));
|
||||
}
|
||||
else
|
||||
_sessionStore.AddUnused(session);
|
||||
}
|
||||
|
||||
if (!response.Sent)
|
||||
response.Send();
|
||||
|
||||
_logWriter.Write(this, LogPrio.Trace, "....done.");
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
|
||||
_sessionStore = _components.Get<IHttpSessionStore>();
|
||||
if (_sessionStore == null)
|
||||
{
|
||||
WriteLog(this, LogPrio.Info, "Defaulting to memory session store.");
|
||||
_sessionStore = new MemorySessionStore();
|
||||
}
|
||||
|
||||
// add default decoders if none have been added.
|
||||
if (FormDecoderProviders.Count == 0)
|
||||
{
|
||||
WriteLog(this, LogPrio.Info, "Loading UrlDecoder, XmlDecoder and MultipartDecoder, since no decoders have been added.");
|
||||
_formDecodersProvider.Add(new UrlDecoder());
|
||||
_formDecodersProvider.Add(new MultipartDecoder());
|
||||
_formDecodersProvider.Add(new XmlDecoder());
|
||||
}
|
||||
|
||||
// Components for building requests.
|
||||
if (!_components.Contains(typeof(IRequestParserFactory)))
|
||||
_components.Add<IRequestParserFactory, RequestParserFactory>();
|
||||
if (!_components.Contains(typeof(IHttpContextFactory)))
|
||||
_components.AddInstance<IHttpContextFactory>(new HttpContextFactory(LogWriter, _components.Get<IRequestParserFactory>()));
|
||||
|
||||
// the special folder does not exist on mono
|
||||
string tempPath = Environment.GetFolderPath(Environment.SpecialFolder.InternetCache);
|
||||
if (string.IsNullOrEmpty(tempPath))
|
||||
tempPath = "/var/tmp/";
|
||||
if (!Directory.Exists(tempPath))
|
||||
{
|
||||
WriteLog(this, LogPrio.Warning, "Temp path do not exist: " + tempPath);
|
||||
return;
|
||||
}
|
||||
DirectoryInfo info = new DirectoryInfo(tempPath);
|
||||
foreach (FileInfo file in info.GetFiles("*.tmp"))
|
||||
file.Delete();
|
||||
|
||||
_requestQueue.Start();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Can be overloaded to implement stuff when a client have been connected.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Default implementation does nothing.
|
||||
/// </remarks>
|
||||
/// <param name="client">client that disconnected</param>
|
||||
/// <param name="error">disconnect reason</param>
|
||||
protected virtual void OnClientDisconnected(IHttpClientContext client, SocketError error)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle authentication
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="session"></param>
|
||||
/// <returns>true if request can be handled; false if not.</returns>
|
||||
/// <exception cref="BadRequestException">Invalid authorization header</exception>
|
||||
protected virtual bool ProcessAuthentication(IHttpRequest request, IHttpResponse response, IHttpSession session)
|
||||
{
|
||||
if (_authModules.Count > 0)
|
||||
{
|
||||
bool authenticate = false;
|
||||
object authTag = null;
|
||||
if (request.Headers["authorization"] != null)
|
||||
{
|
||||
authenticate = true;
|
||||
string authHeader = request.Headers["authorization"];
|
||||
int pos = authHeader.IndexOf(' ');
|
||||
if (pos == -1)
|
||||
throw new BadRequestException("Invalid authorization header");
|
||||
// first word identifies the type of authentication to use.
|
||||
string word = authHeader.Substring(0, pos).ToLower();
|
||||
|
||||
// find the mod to use.
|
||||
AuthenticationModule mod = null;
|
||||
lock (_authModules)
|
||||
{
|
||||
foreach (AuthenticationModule aModule in _authModules)
|
||||
{
|
||||
if (aModule.Name != word)
|
||||
continue;
|
||||
mod = aModule;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mod != null)
|
||||
{
|
||||
authTag = mod.Authenticate(authHeader, GetRealm(request), request.Method);
|
||||
session[AuthenticationModule.AuthenticationTag] = authTag;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check if auth is needed.
|
||||
if (authTag == null)
|
||||
{
|
||||
lock (_authModules)
|
||||
{
|
||||
foreach (AuthenticationModule module in _authModules)
|
||||
{
|
||||
if (!module.AuthenticationRequired(request))
|
||||
continue;
|
||||
|
||||
RequestAuthentication(module, request, response);
|
||||
return false;
|
||||
}
|
||||
|
||||
// modules can have inited the authentication
|
||||
// and then the module.AuthenticationRequired method will not have been used.
|
||||
if (authenticate && _authModules.Count > 0)
|
||||
{
|
||||
RequestAuthentication(_authModules[0], request, response);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will request authentication.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sends respond to client, nothing else can be done with the response after this.
|
||||
/// </remarks>
|
||||
/// <param name="mod"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="response"></param>
|
||||
protected virtual void RequestAuthentication(AuthenticationModule mod, IHttpRequest request, IHttpResponse response)
|
||||
{
|
||||
string theResponse = mod.CreateResponse(GetRealm(request));
|
||||
response.AddHeader("www-authenticate", theResponse);
|
||||
response.Reason = "Authentication required.";
|
||||
response.Status = HttpStatusCode.Unauthorized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Received from a <see cref="IHttpClientContext"/> when a request have been parsed successfully.
|
||||
/// </summary>
|
||||
/// <param name="source"><see cref="IHttpClientContext"/> that received the request.</param>
|
||||
/// <param name="args">The request.</param>
|
||||
private void OnRequest(object source, RequestEventArgs args)
|
||||
{
|
||||
_current = this;
|
||||
IHttpClientContext context = (IHttpClientContext) source;
|
||||
IHttpRequest request = args.Request;
|
||||
if (_requestQueue.ShouldQueue)
|
||||
{
|
||||
_requestQueue.Enqueue(context, request);
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessRequestWrapper(context, request);
|
||||
|
||||
// no need to lock, if all threads are busy,
|
||||
// someone is bound to trigger the thread correctly =)
|
||||
_requestQueue.Trigger();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be able to track request count.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="request"></param>
|
||||
private void ProcessRequestWrapper(IHttpClientContext context, IHttpRequest request)
|
||||
{
|
||||
_requestQueue.CurrentRequestCount += 1;
|
||||
ProcessRequest(context, request);
|
||||
_requestQueue.CurrentRequestCount -= 1;
|
||||
}
|
||||
|
||||
private void ProcessRequest(IHttpClientContext context, IHttpRequest request)
|
||||
{
|
||||
IHttpResponse response = request.CreateResponse(context);
|
||||
try
|
||||
{
|
||||
foreach (IRule rule in _rules)
|
||||
{
|
||||
if (!rule.Process(request, response))
|
||||
continue;
|
||||
response.Send();
|
||||
return;
|
||||
}
|
||||
|
||||
// load cookies if the exist.
|
||||
RequestCookies cookies = request.Headers["cookie"] != null
|
||||
? new RequestCookies(request.Headers["cookie"])
|
||||
: new RequestCookies(string.Empty);
|
||||
|
||||
request.SetCookies(cookies);
|
||||
|
||||
IHttpSession session;
|
||||
if (cookies[_sessionCookieName] != null)
|
||||
{
|
||||
string sessionCookie = cookies[_sessionCookieName].Value;
|
||||
|
||||
// there's a bug somewhere which fucks up headers which can render the session cookie useless.
|
||||
// therefore let's consider the session cookie as not set if that have happened.
|
||||
if (sessionCookie.Length > 40)
|
||||
{
|
||||
_logWriter.Write(this, LogPrio.Error, "Session cookie is invalid: " + sessionCookie);
|
||||
cookies.Remove(_sessionCookieName);
|
||||
_sessionStore.Remove(sessionCookie); // free the session cookie (and thus generating a new one).
|
||||
session = _sessionStore.Create();
|
||||
}
|
||||
else
|
||||
session = _sessionStore.Load(sessionCookie) ??
|
||||
_sessionStore.Create(sessionCookie);
|
||||
}
|
||||
else
|
||||
session = _sessionStore.Create();
|
||||
|
||||
HandleRequest(context, request, response, session);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
if (_exceptionHandler == null)
|
||||
#if DEBUG
|
||||
throw;
|
||||
#else
|
||||
{
|
||||
WriteLog(LogPrio.Fatal, err.Message);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
_exceptionHandler(this, err);
|
||||
|
||||
Exception e = err;
|
||||
while (e != null)
|
||||
{
|
||||
if (e is SocketException)
|
||||
return;
|
||||
|
||||
e = e.InnerException;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
context.Respond("HTTP/1.0", HttpStatusCode.InternalServerError, "Internal server error", err.ToString(), "text/plain");
|
||||
#else
|
||||
context.Respond("HTTP/1.0", HttpStatusCode.InternalServerError, "Internal server error");
|
||||
#endif
|
||||
}
|
||||
catch (Exception err2)
|
||||
{
|
||||
LogWriter.Write(this, LogPrio.Fatal, "Failed to respond on message with Internal Server Error: " + err2);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the web server using regular HTTP.
|
||||
/// </summary>
|
||||
/// <param name="address">IP Address to listen on, use <c>IpAddress.Any </c>to accept connections on all IP addresses/network cards.</param>
|
||||
/// <param name="port">Port to listen on. 80 can be a good idea =)</param>
|
||||
/// <exception cref="ArgumentNullException"><c>address</c> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Port must be a positive number.</exception>
|
||||
public void Start(IPAddress address, int port)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException("address");
|
||||
if (port <= 0)
|
||||
throw new ArgumentException("Port must be a positive number.");
|
||||
if (_httpListener != null)
|
||||
return;
|
||||
|
||||
|
||||
Init();
|
||||
_httpListener = new HttpListener(address, port, _components.Get<IHttpContextFactory>()) { LogWriter = LogWriter };
|
||||
_httpListener.RequestReceived += OnRequest;
|
||||
_httpListener.Start(Int32.MaxValue);
|
||||
_httpListener.ExceptionThrown += _exceptionHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accept secure connections.
|
||||
/// </summary>
|
||||
/// <param name="address">IP Address to listen on, use <see cref="IPAddress.Any"/> to accept connections on all IP Addresses / network cards.</param>
|
||||
/// <param name="port">Port to listen on. 80 can be a good idea =)</param>
|
||||
/// <param name="certificate">Certificate to use</param>
|
||||
/// <exception cref="ArgumentNullException"><c>address</c> is null.</exception>
|
||||
/// <exception cref="ArgumentException">Port must be a positive number.</exception>
|
||||
public void Start(IPAddress address, int port, X509Certificate certificate)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException("address");
|
||||
if (port <= 0)
|
||||
throw new ArgumentException("Port must be a positive number.");
|
||||
if (_httpsListener != null)
|
||||
return;
|
||||
|
||||
Init();
|
||||
_httpsListener = new HttpListener(address, port, _components.Get<IHttpContextFactory>(), certificate);
|
||||
_httpsListener.LogWriter = LogWriter;
|
||||
_httpsListener.RequestReceived += OnRequest;
|
||||
_httpsListener.Start(Int32.MaxValue);
|
||||
_httpsListener.ExceptionThrown += _exceptionHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// shut down the server and listeners
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (_httpListener != null)
|
||||
{
|
||||
_httpListener.Stop();
|
||||
_httpListener = null;
|
||||
}
|
||||
if (_httpsListener != null)
|
||||
{
|
||||
_httpsListener.Stop();
|
||||
_httpsListener = null;
|
||||
}
|
||||
|
||||
_requestQueue.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write an entry to the log file
|
||||
/// </summary>
|
||||
/// <param name="prio">importance of the message</param>
|
||||
/// <param name="message">log message</param>
|
||||
protected virtual void WriteLog(LogPrio prio, string message)
|
||||
{
|
||||
LogWriter.Write(this, prio, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write an entry to the log file
|
||||
/// </summary>
|
||||
/// <param name="source">object that wrote the message</param>
|
||||
/// <param name="prio">importance of the message</param>
|
||||
/// <param name="message">log message</param>
|
||||
public void WriteLog(object source, LogPrio prio, string message)
|
||||
{
|
||||
LogWriter.Write(source, prio, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Realms are used during HTTP authentication.
|
||||
/// Default realm is same as server name.
|
||||
/// </summary>
|
||||
public event RealmHandler RealmWanted;
|
||||
|
||||
/// <summary>
|
||||
/// Let's to receive unhandled exceptions from the threads.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Exceptions will be thrown during debug mode if this event is not used,
|
||||
/// exceptions will be printed to console and suppressed during release mode.
|
||||
/// </remarks>
|
||||
public event ExceptionHandler ExceptionThrown
|
||||
{
|
||||
add
|
||||
{
|
||||
_exceptionHandler += value;
|
||||
if (_httpListener != null)
|
||||
_httpListener.ExceptionThrown += value;
|
||||
if (_httpsListener != null)
|
||||
_httpsListener.ExceptionThrown += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
_exceptionHandler -= value;
|
||||
if (_httpListener != null)
|
||||
_httpListener.ExceptionThrown -= value;
|
||||
if (_httpsListener != null)
|
||||
_httpsListener.ExceptionThrown -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{455E7D70-1C85-4D7F-9F01-DC801B8B8C34}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>HttpServer_OpenSim</RootNamespace>
|
||||
<AssemblyName>HttpServer_OpenSim</AssemblyName>
|
||||
<StartupObject>
|
||||
</StartupObject>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>test.snk</AssemblyOriginatorKeyFile>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<UpgradeBackupLocation>
|
||||
</UpgradeBackupLocation>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile />
|
||||
</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>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
<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>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Test|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\Test\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;TEST</DefineConstants>
|
||||
<DocumentationFile>bin\Debug\HttpServer.XML</DocumentationFile>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Authentication\AuthenticationModule.cs" />
|
||||
<Compile Include="Authentication\BasicAuthentication.cs" />
|
||||
<Compile Include="Authentication\DigestAuthentication.cs" />
|
||||
<Compile Include="Check.cs" />
|
||||
<Compile Include="ClientAcceptedEventArgs.cs" />
|
||||
<Compile Include="ComponentProvider.cs" />
|
||||
<Compile Include="ContentTypes.cs" />
|
||||
<Compile Include="ContextTimeoutManager.cs" />
|
||||
<Compile Include="ExceptionHandler.cs" />
|
||||
<Compile Include="Exceptions\BadRequestException.cs" />
|
||||
<Compile Include="Exceptions\ForbiddenException.cs" />
|
||||
<Compile Include="Exceptions\HttpException.cs" />
|
||||
<Compile Include="Exceptions\InternalServerException.cs" />
|
||||
<Compile Include="Exceptions\NotFoundException.cs" />
|
||||
<Compile Include="Exceptions\UnauthorizedException.cs" />
|
||||
<Compile Include="FormDecoders\FormDecoderProvider.cs" />
|
||||
<Compile Include="FormDecoders\HttpMultipart.cs" />
|
||||
<Compile Include="FormDecoders\IFormDecoder.cs" />
|
||||
<Compile Include="FormDecoders\MultipartDecoder.cs" />
|
||||
<Compile Include="FormDecoders\UrlDecoder.cs" />
|
||||
<Compile Include="FormDecoders\XmlDecoder.cs" />
|
||||
<Compile Include="Helpers\FormHelper.cs" />
|
||||
<Compile Include="Helpers\GetIdTitle.cs" />
|
||||
<Compile Include="Helpers\Implementations\PrototypeImp.cs" />
|
||||
<Compile Include="Helpers\JSHelper.cs" />
|
||||
<Compile Include="Helpers\ObjectForm.cs" />
|
||||
<Compile Include="Helpers\ResourceInfo.cs" />
|
||||
<Compile Include="Helpers\ResourceManager.cs" />
|
||||
<Compile Include="Helpers\WebHelper.cs" />
|
||||
<Compile Include="Helpers\XmlHelper.cs" />
|
||||
<Compile Include="HttpClientContext.cs" />
|
||||
<Compile Include="HttpContextFactory.cs" />
|
||||
<Compile Include="HttpFile.cs" />
|
||||
<Compile Include="HttpForm.cs" />
|
||||
<Compile Include="HttpHelper.cs" />
|
||||
<Compile Include="HttpInput.cs" />
|
||||
<Compile Include="HttpInputItem.cs" />
|
||||
<Compile Include="HttpListener.cs" />
|
||||
<Compile Include="HttpListenerBase.cs" />
|
||||
<Compile Include="HttpModules\FileModule.cs" />
|
||||
<Compile Include="HttpModules\HttpModule.cs" />
|
||||
<Compile Include="HttpModules\HttpModuleExceptionEventArgs.cs" />
|
||||
<Compile Include="HttpModules\ResourceFileModule.cs" />
|
||||
<Compile Include="HttpModules\ReverseProxyModule.cs" />
|
||||
<Compile Include="HttpModules\WebSiteModule.cs" />
|
||||
<Compile Include="HttpParam.cs" />
|
||||
<Compile Include="HttpRequest.cs" />
|
||||
<Compile Include="HttpResponse.cs" />
|
||||
<Compile Include="HttpServer.cs" />
|
||||
<Compile Include="IComponentProvider.cs" />
|
||||
<Compile Include="IHttpClientContext.cs" />
|
||||
<Compile Include="IHttpContextHandler.cs" />
|
||||
<Compile Include="IHttpRequest.cs" />
|
||||
<Compile Include="IHttpRequestParser.cs" />
|
||||
<Compile Include="IHttpResponse.cs" />
|
||||
<Compile Include="ILogWriter.cs" />
|
||||
<Compile Include="Method.cs" />
|
||||
<Compile Include="Parser\BodyEventArgs.cs" />
|
||||
<Compile Include="Parser\HeaderEventArgs.cs" />
|
||||
<Compile Include="Parser\HttpRequestParser.cs" />
|
||||
<Compile Include="Parser\RequestLineEventArgs.cs" />
|
||||
<Compile Include="Parser\RequestParserFactory.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="RequestCookie.cs" />
|
||||
<Compile Include="RequestCookies.cs" />
|
||||
<Compile Include="RequestQueue.cs" />
|
||||
<Compile Include="ResponseCookie.cs" />
|
||||
<Compile Include="ResponseCookies.cs" />
|
||||
<Compile Include="Rules\IRule.cs" />
|
||||
<Compile Include="Rules\RedirectRule.cs" />
|
||||
<Compile Include="Rules\RegexRedirectRule.cs" />
|
||||
<Compile Include="Sessions\IHttpSession.cs" />
|
||||
<Compile Include="Sessions\IHttpSessionStore.cs" />
|
||||
<Compile Include="Sessions\MemorySession.cs" />
|
||||
<Compile Include="Sessions\MemorySessionStore.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="httpserver.pfx" />
|
||||
<None Include="test.snk" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.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>
|
||||
-->
|
||||
</Project>
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Inversion of control interface.
|
||||
/// </summary>
|
||||
public interface IComponentProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Add a component instance
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Interface type</typeparam>
|
||||
/// <param name="instance">Instance to add</param>
|
||||
void AddInstance<T>(object instance);
|
||||
|
||||
/// <summary>
|
||||
/// Get a component.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Interface type</typeparam>
|
||||
/// <returns>Component if registered, otherwise null.</returns>
|
||||
/// <remarks>
|
||||
/// Component will get created if needed.
|
||||
/// </remarks>
|
||||
T Get<T>() where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified component interface have been added.
|
||||
/// </summary>
|
||||
/// <param name="interfaceType"></param>
|
||||
/// <returns>true if found; otherwise false.</returns>
|
||||
bool Contains(Type interfaceType);
|
||||
|
||||
/// <summary>
|
||||
/// Add a component.
|
||||
/// </summary>
|
||||
/// <typeparam name="InterfaceType">Type being requested.</typeparam>
|
||||
/// <typeparam name="InstanceType">Type being created.</typeparam>
|
||||
void Add<InterfaceType, InstanceType>();
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains a connection to a browser/client.
|
||||
/// </summary>
|
||||
public interface IHttpClientContext
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Get SSL commonName of remote peer
|
||||
/// </summary>
|
||||
string SSLCommonName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Using SSL or other encryption method.
|
||||
/// </summary>
|
||||
[Obsolete("Use IsSecured instead.")]
|
||||
bool Secured { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Using SSL or other encryption method.
|
||||
/// </summary>
|
||||
bool IsSecured { get; }
|
||||
|
||||
int contextID {get;}
|
||||
int TimeoutKeepAlive {get; set; }
|
||||
int MAXRequests{get; set; }
|
||||
|
||||
bool CanSend();
|
||||
bool IsSending();
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from client
|
||||
/// </summary>
|
||||
/// <param name="error">error to report in the <see cref="Disconnected"/> event.</param>
|
||||
void Disconnect(SocketError error);
|
||||
|
||||
/// <summary>
|
||||
/// Send a response.
|
||||
/// </summary>
|
||||
/// <param name="httpVersion">Either <see cref="HttpHelper.HTTP10"/> or <see cref="HttpHelper.HTTP11"/></param>
|
||||
/// <param name="statusCode">HTTP status code</param>
|
||||
/// <param name="reason">reason for the status code.</param>
|
||||
/// <param name="body">HTML body contents, can be null or empty.</param>
|
||||
/// <param name="contentType">A content type to return the body as, i.e. 'text/html' or 'text/plain', defaults to 'text/html' if null or empty</param>
|
||||
/// <exception cref="ArgumentException">If <paramref name="httpVersion"/> is invalid.</exception>
|
||||
void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType);
|
||||
|
||||
/// <summary>
|
||||
/// Send a response.
|
||||
/// </summary>
|
||||
/// <param name="httpVersion">Either <see cref="HttpHelper.HTTP10"/> or <see cref="HttpHelper.HTTP11"/></param>
|
||||
/// <param name="statusCode">HTTP status code</param>
|
||||
/// <param name="reason">reason for the status code.</param>
|
||||
void Respond(string httpVersion, HttpStatusCode statusCode, string reason);
|
||||
|
||||
/// <summary>
|
||||
/// Send a response.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
void Respond(string body);
|
||||
|
||||
/// <summary>
|
||||
/// send a whole buffer
|
||||
/// </summary>
|
||||
/// <param name="buffer">buffer to send</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
bool Send(byte[] buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Send data using the stream
|
||||
/// </summary>
|
||||
/// <param name="buffer">Contains data to send</param>
|
||||
/// <param name="offset">Start position in buffer</param>
|
||||
/// <param name="size">number of bytes to send</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
bool Send(byte[] buffer, int offset, int size);
|
||||
Task<bool> SendAsync(byte[] buffer, int offset, int size);
|
||||
|
||||
/// <summary>
|
||||
/// Closes the streams and disposes of the unmanaged resources
|
||||
/// </summary>
|
||||
void Close();
|
||||
|
||||
/// <summary>
|
||||
/// The context have been disconnected.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Event can be used to clean up a context, or to reuse it.
|
||||
/// </remarks>
|
||||
event EventHandler<DisconnectedEventArgs> Disconnected;
|
||||
|
||||
/// <summary>
|
||||
/// A request have been received in the context.
|
||||
/// </summary>
|
||||
event EventHandler<RequestEventArgs> RequestReceived;
|
||||
|
||||
HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing();
|
||||
|
||||
void StartSendResponse(HttpResponse response);
|
||||
void ContinueSendResponse();
|
||||
void ReqResponseAboutToSend(uint requestID);
|
||||
void ReqResponseSent(uint requestID, ConnectionType connection);
|
||||
bool TrySendResponse(int limit);
|
||||
}
|
||||
public class HTTPNetworkContext
|
||||
{
|
||||
public NetworkStream Stream;
|
||||
public Socket Socket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="IHttpClientContext"/> have been disconnected.
|
||||
/// </summary>
|
||||
public class DisconnectedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets reason to why client disconnected.
|
||||
/// </summary>
|
||||
public SocketError Error { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DisconnectedEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="error">Reason to disconnection.</param>
|
||||
public DisconnectedEventArgs(SocketError error)
|
||||
{
|
||||
Check.Require(error, "error");
|
||||
|
||||
Error = error;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class RequestEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets received request.
|
||||
/// </summary>
|
||||
public IHttpRequest Request { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
public RequestEventArgs(IHttpRequest request)
|
||||
{
|
||||
Check.Require(request, "request");
|
||||
|
||||
Request = request;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that receives Requests from a <see cref="IHttpClientContext"/>.
|
||||
/// </summary>
|
||||
public interface IHttpContextHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Client have been disconnected.
|
||||
/// </summary>
|
||||
/// <param name="client">Client that was disconnected.</param>
|
||||
/// <param name="error">Reason</param>
|
||||
/// <see cref="IHttpClientContext"/>
|
||||
void ClientDisconnected(IHttpClientContext client, SocketError error);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a client context have received a new HTTP request
|
||||
/// </summary>
|
||||
/// <param name="client">Client that received the request.</param>
|
||||
/// <param name="request">Request that was received.</param>
|
||||
/// <see cref="IHttpClientContext"/>
|
||||
void RequestReceived(IHttpClientContext client, IHttpRequest request);
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.FormDecoders;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains server side HTTP request information.
|
||||
/// </summary>
|
||||
public interface IHttpRequest : ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets kind of types accepted by the client.
|
||||
/// </summary>
|
||||
string[] AcceptTypes { get; }
|
||||
|
||||
uint ID {get; }
|
||||
/// <summary>
|
||||
/// Gets or sets body stream.
|
||||
/// </summary>
|
||||
Stream Body { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the body is complete.
|
||||
/// </summary>
|
||||
bool BodyIsComplete { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets kind of connection used for the session.
|
||||
/// </summary>
|
||||
ConnectionType Connection { get; set; }
|
||||
|
||||
IHttpClientContext Context { get; }
|
||||
/// <summary>
|
||||
/// Gets or sets number of bytes in the body.
|
||||
/// </summary>
|
||||
int ContentLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets cookies that was sent with the request.
|
||||
/// </summary>
|
||||
RequestCookies Cookies { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets form parameters.
|
||||
/// </summary>
|
||||
HttpForm Form { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets headers sent by the client.
|
||||
/// </summary>
|
||||
NameValueCollection Headers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets version of HTTP protocol that's used.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Probably <see cref="HttpHelper.HTTP10"/> or <see cref="HttpHelper.HTTP11"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="HttpHelper"/>
|
||||
string HttpVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the request was made by Ajax (Asynchronous JavaScript)
|
||||
/// </summary>
|
||||
bool IsAjax { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets requested method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Will always be in upper case.
|
||||
/// </remarks>
|
||||
/// <see cref="Method"/>
|
||||
string Method { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets parameter from <see cref="QueryString"/> or <see cref="Form"/>.
|
||||
/// </summary>
|
||||
HttpParam Param { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets variables sent in the query string
|
||||
/// </summary>
|
||||
HttpInput QueryString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets requested URI.
|
||||
/// </summary>
|
||||
Uri Uri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets URI absolute path divided into parts.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// // URI is: http://gauffin.com/code/tiny/
|
||||
/// Console.WriteLine(request.UriParts[0]); // result: code
|
||||
/// Console.WriteLine(request.UriParts[1]); // result: tiny
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// If you're using controllers than the first part is controller name,
|
||||
/// the second part is method name and the third part is Id property.
|
||||
/// </remarks>
|
||||
/// <seealso cref="Uri"/>
|
||||
string[] UriParts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets path and query.
|
||||
/// </summary>
|
||||
/// <see cref="Uri"/>
|
||||
/// <remarks>
|
||||
/// Are only used during request parsing. Cannot be set after "Host" header have been
|
||||
/// added.
|
||||
/// </remarks>
|
||||
string UriPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called during parsing of a <see cref="IHttpRequest"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the header, should not be URL encoded</param>
|
||||
/// <param name="value">Value of the header, should not be URL encoded</param>
|
||||
/// <exception cref="BadRequestException">If a header is incorrect.</exception>
|
||||
void AddHeader(string name, string value);
|
||||
|
||||
/// <summary>
|
||||
/// Add bytes to the body
|
||||
/// </summary>
|
||||
/// <param name="bytes">buffer to read bytes from</param>
|
||||
/// <param name="offset">where to start read</param>
|
||||
/// <param name="length">number of bytes to read</param>
|
||||
/// <returns>Number of bytes actually read (same as length unless we got all body bytes).</returns>
|
||||
/// <exception cref="InvalidOperationException">If body is not writable</exception>
|
||||
/// <exception cref="ArgumentNullException"><c>bytes</c> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><c>offset</c> is out of range.</exception>
|
||||
int AddToBody(byte[] bytes, int offset, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Clear everything in the request
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Decode body into a form.
|
||||
/// </summary>
|
||||
/// <param name="providers">A list with form decoders.</param>
|
||||
/// <exception cref="InvalidDataException">If body contents is not valid for the chosen decoder.</exception>
|
||||
/// <exception cref="InvalidOperationException">If body is still being transferred.</exception>
|
||||
void DecodeBody(FormDecoderProvider providers);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the cookies.
|
||||
/// </summary>
|
||||
/// <param name="cookies">The cookies.</param>
|
||||
void SetCookies(RequestCookies cookies);
|
||||
|
||||
/// <summary>
|
||||
/// Create a response object.
|
||||
/// </summary>
|
||||
/// <param name="context">Context for the connected client.</param>
|
||||
/// <returns>A new <see cref="IHttpResponse"/>.</returns>
|
||||
IHttpResponse CreateResponse(IHttpClientContext context);
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using System;
|
||||
using HttpServer.Exceptions;
|
||||
using HttpServer.Parser;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Event driven parser used to parse incoming HTTP requests.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The parser supports partial messages and keeps the states between
|
||||
/// each parsed buffer. It's therefore important that the parser gets
|
||||
/// <see cref="Clear"/>ed if a client disconnects.
|
||||
/// </remarks>
|
||||
public interface IHttpRequestParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Current state in parser.
|
||||
/// </summary>
|
||||
RequestParserState CurrentState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Parse partial or complete message.
|
||||
/// </summary>
|
||||
/// <param name="buffer">buffer containing incoming bytes</param>
|
||||
/// <param name="offset">where in buffer that parsing should start</param>
|
||||
/// <param name="count">number of bytes to parse</param>
|
||||
/// <returns>Unparsed bytes left in buffer.</returns>
|
||||
/// <exception cref="BadRequestException"><c>BadRequestException</c>.</exception>
|
||||
int Parse(byte[] buffer, int offset, int count);
|
||||
|
||||
/// <summary>
|
||||
/// A request have been successfully parsed.
|
||||
/// </summary>
|
||||
event EventHandler RequestCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// More body bytes have been received.
|
||||
/// </summary>
|
||||
event EventHandler<BodyEventArgs> BodyBytesReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Request line have been received.
|
||||
/// </summary>
|
||||
event EventHandler<RequestLineEventArgs> RequestLineReceived;
|
||||
|
||||
/// <summary>
|
||||
/// A header have been received.
|
||||
/// </summary>
|
||||
event EventHandler<HeaderEventArgs> HeaderReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Clear parser state.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the log writer.
|
||||
/// </summary>
|
||||
ILogWriter LogWriter { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current state in the parsing.
|
||||
/// </summary>
|
||||
public enum RequestParserState
|
||||
{
|
||||
/// <summary>
|
||||
/// Should parse the request line
|
||||
/// </summary>
|
||||
FirstLine,
|
||||
/// <summary>
|
||||
/// Searching for a complete header name
|
||||
/// </summary>
|
||||
HeaderName,
|
||||
/// <summary>
|
||||
/// Searching for colon after header name (ignoring white spaces)
|
||||
/// </summary>
|
||||
AfterName,
|
||||
/// <summary>
|
||||
/// Searching for start of header value (ignoring white spaces)
|
||||
/// </summary>
|
||||
Between,
|
||||
/// <summary>
|
||||
/// Searching for a complete header value (can span over multiple lines, as long as they are prefixed with one/more whitespaces)
|
||||
/// </summary>
|
||||
HeaderValue,
|
||||
|
||||
/// <summary>
|
||||
/// Adding bytes to body
|
||||
/// </summary>
|
||||
Body
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Response that is sent back to the web browser / client.
|
||||
///
|
||||
/// A response can be sent if different ways. The easiest one is
|
||||
/// to just fill the Body stream with content, everything else
|
||||
/// will then be taken care of by the framework. The default content-type
|
||||
/// is text/html, you should change it if you send anything else.
|
||||
///
|
||||
/// The second and slighty more complex way is to send the response
|
||||
/// as parts. Start with sending the header using the SendHeaders method and
|
||||
/// then you can send the body using SendBody method, but do not forget
|
||||
/// to set ContentType and ContentLength before doing so.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// public void MyHandler(IHttpRequest request, IHttpResponse response)
|
||||
/// {
|
||||
///
|
||||
/// }
|
||||
/// </example>
|
||||
public interface IHttpResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The body stream is used to cache the body contents
|
||||
/// before sending everything to the client. It's the simplest
|
||||
/// way to serve documents.
|
||||
/// </summary>
|
||||
Stream Body { get; set; }
|
||||
byte[] RawBuffer { get; set; }
|
||||
int RawBufferStart { get; set; }
|
||||
int RawBufferLen { get; set; }
|
||||
uint requestID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines the version of the HTTP Response for applications where it's required
|
||||
/// for this to be forced.
|
||||
/// </summary>
|
||||
string ProtocolVersion { get; set; }
|
||||
int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The chunked encoding modifies the body of a message in order to
|
||||
/// transfer it as a series of chunks, each with its own size indicator,
|
||||
/// followed by an OPTIONAL trailer containing entity-header fields. This
|
||||
/// allows dynamically produced content to be transferred along with the
|
||||
/// information necessary for the recipient to verify that it has
|
||||
/// received the full message.
|
||||
/// </summary>
|
||||
bool Chunked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Kind of connection
|
||||
/// </summary>
|
||||
ConnectionType Connection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encoding to use when sending stuff to the client.
|
||||
/// </summary>
|
||||
/// <remarks>Default is UTF8</remarks>
|
||||
Encoding Encoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of seconds to keep connection alive
|
||||
/// </summary>
|
||||
/// <remarks>Only used if Connection property is set to ConnectionType.KeepAlive</remarks>
|
||||
int KeepAlive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Status code that is sent to the client.
|
||||
/// </summary>
|
||||
/// <remarks>Default is HttpStatusCode.Ok</remarks>
|
||||
HttpStatusCode Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about why a specific status code was used.
|
||||
/// </summary>
|
||||
string Reason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the body. MUST be specified before sending the header,
|
||||
/// unless property Chunked is set to true.
|
||||
/// </summary>
|
||||
long ContentLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Kind of content in the body
|
||||
/// </summary>
|
||||
/// <remarks>Default is text/html</remarks>
|
||||
string ContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Headers have been sent to the client-
|
||||
/// </summary>
|
||||
/// <remarks>You can not send any additional headers if they have already been sent.</remarks>
|
||||
bool HeadersSent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The whole response have been sent.
|
||||
/// </summary>
|
||||
bool Sent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Cookies that should be created/changed.
|
||||
/// </summary>
|
||||
ResponseCookies Cookies { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Add another header to the document.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the header, case sensitive, use lower cases.</param>
|
||||
/// <param name="value">Header values can span over multiple lines as long as each line starts with a white space. New line chars should be \r\n</param>
|
||||
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
|
||||
/// <exception cref="ArgumentException">If value conditions have not been met.</exception>
|
||||
/// <remarks>Adding any header will override the default ones and those specified by properties.</remarks>
|
||||
void AddHeader(string name, string value);
|
||||
|
||||
/// <summary>
|
||||
/// Send headers and body to the browser.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If content have already been sent.</exception>
|
||||
void Send();
|
||||
|
||||
/// <summary>
|
||||
/// Make sure that you have specified ContentLength and sent the headers first.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
|
||||
/// <see cref="IHttpResponse.SendHeaders"/>
|
||||
/// <param name="offset">offest of first byte to send</param>
|
||||
/// <param name="count">number of bytes to send.</param>
|
||||
/// <seealso cref="IHttpResponse.Send"/>
|
||||
/// <seealso cref="IHttpResponse.SendHeaders"/>
|
||||
/// <remarks>This method can be used if you want to send body contents without caching them first. This
|
||||
/// is recommended for larger files to keep the memory usage low.</remarks>
|
||||
bool SendBody(byte[] buffer, int offset, int count);
|
||||
|
||||
/// <summary>
|
||||
/// Make sure that you have specified ContentLength and sent the headers first.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
|
||||
/// <see cref="IHttpResponse.SendHeaders"/>
|
||||
/// <seealso cref="IHttpResponse.Send"/>
|
||||
/// <seealso cref="IHttpResponse.SendHeaders"/>
|
||||
/// <remarks>This method can be used if you want to send body contents without caching them first. This
|
||||
/// is recommended for larger files to keep the memory usage low.</remarks>
|
||||
bool SendBody(byte[] buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Send headers to the client.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
|
||||
/// <seealso cref="IHttpResponse.AddHeader"/>
|
||||
/// <seealso cref="IHttpResponse.Send"/>
|
||||
/// <seealso cref="IHttpResponse.SendBody(byte[])"/>
|
||||
bool SendHeaders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of HTTP connection
|
||||
/// </summary>
|
||||
public enum ConnectionType
|
||||
{
|
||||
/// <summary>
|
||||
/// Connection is closed after each request-response
|
||||
/// </summary>
|
||||
Close,
|
||||
|
||||
/// <summary>
|
||||
/// Connection is kept alive for X seconds (unless another request have been made)
|
||||
/// </summary>
|
||||
KeepAlive
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Priority for log entries
|
||||
/// </summary>
|
||||
/// <seealso cref="ILogWriter"/>
|
||||
public enum LogPrio
|
||||
{
|
||||
/// <summary>
|
||||
/// Very detailed logs to be able to follow the flow of the program.
|
||||
/// </summary>
|
||||
Trace,
|
||||
|
||||
/// <summary>
|
||||
/// Logs to help debug errors in the application
|
||||
/// </summary>
|
||||
Debug,
|
||||
|
||||
/// <summary>
|
||||
/// Information to be able to keep track of state changes etc.
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// Something did not go as we expected, but it's no problem.
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// Something that should not fail failed, but we can still keep
|
||||
/// on going.
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Something failed, and we cannot handle it properly.
|
||||
/// </summary>
|
||||
Fatal
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface used to write to log files.
|
||||
/// </summary>
|
||||
public interface ILogWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Write an entry to the log file.
|
||||
/// </summary>
|
||||
/// <param name="source">object that is writing to the log</param>
|
||||
/// <param name="priority">importance of the log message</param>
|
||||
/// <param name="message">the message</param>
|
||||
void Write(object source, LogPrio priority, string message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class writes to the console. It colors the output depending on the logprio and includes a 3-level stacktrace (in debug mode)
|
||||
/// </summary>
|
||||
/// <seealso cref="ILogWriter"/>
|
||||
public sealed class ConsoleLogWriter : ILogWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// The actual instance of this class.
|
||||
/// </summary>
|
||||
public static readonly ConsoleLogWriter Instance = new ConsoleLogWriter();
|
||||
|
||||
/// <summary>
|
||||
/// Logwriters the specified source.
|
||||
/// </summary>
|
||||
/// <param name="source">object that wrote the logentry.</param>
|
||||
/// <param name="prio">Importance of the log message</param>
|
||||
/// <param name="message">The message.</param>
|
||||
public void Write(object source, LogPrio prio, string message)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(DateTime.Now.ToString());
|
||||
sb.Append(" ");
|
||||
sb.Append(prio.ToString().PadRight(10));
|
||||
sb.Append(" | ");
|
||||
#if DEBUG
|
||||
StackTrace trace = new StackTrace();
|
||||
StackFrame[] frames = trace.GetFrames();
|
||||
int endFrame = frames.Length > 4 ? 4 : frames.Length;
|
||||
int startFrame = frames.Length > 0 ? 1 : 0;
|
||||
for (int i = startFrame; i < endFrame; ++i)
|
||||
{
|
||||
sb.Append(frames[i].GetMethod().Name);
|
||||
sb.Append(" -> ");
|
||||
}
|
||||
#else
|
||||
sb.Append(System.Reflection.MethodBase.GetCurrentMethod().Name);
|
||||
sb.Append(" | ");
|
||||
#endif
|
||||
sb.Append(message);
|
||||
|
||||
Console.ForegroundColor = GetColor(prio);
|
||||
Console.WriteLine(sb.ToString());
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get color for the specified logprio
|
||||
/// </summary>
|
||||
/// <param name="prio">prio for the log entry</param>
|
||||
/// <returns>A <see cref="ConsoleColor"/> for the prio</returns>
|
||||
public static ConsoleColor GetColor(LogPrio prio)
|
||||
{
|
||||
switch (prio)
|
||||
{
|
||||
case LogPrio.Trace:
|
||||
return ConsoleColor.DarkGray;
|
||||
case LogPrio.Debug:
|
||||
return ConsoleColor.Gray;
|
||||
case LogPrio.Info:
|
||||
return ConsoleColor.White;
|
||||
case LogPrio.Warning:
|
||||
return ConsoleColor.DarkMagenta;
|
||||
case LogPrio.Error:
|
||||
return ConsoleColor.Magenta;
|
||||
case LogPrio.Fatal:
|
||||
return ConsoleColor.Red;
|
||||
}
|
||||
|
||||
return ConsoleColor.Yellow;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default log writer, writes everything to null (nowhere).
|
||||
/// </summary>
|
||||
/// <seealso cref="ILogWriter"/>
|
||||
public sealed class NullLogWriter : ILogWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// The logging instance.
|
||||
/// </summary>
|
||||
public static readonly NullLogWriter Instance = new NullLogWriter();
|
||||
|
||||
/// <summary>
|
||||
/// Writes everything to null
|
||||
/// </summary>
|
||||
/// <param name="source">object that wrote the log entry.</param>
|
||||
/// <param name="prio">Importance of the log message</param>
|
||||
/// <param name="message">The message.</param>
|
||||
public void Write(object source, LogPrio prio, string message)
|
||||
{}
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Priority for log entries
|
||||
/// </summary>
|
||||
/// <seealso cref="LogWriter"/>
|
||||
public enum LogPrio
|
||||
{
|
||||
/// <summary>
|
||||
/// Very detailed logs to be able to follow the flow of the program.
|
||||
/// </summary>
|
||||
Trace,
|
||||
|
||||
/// <summary>
|
||||
/// Logs to help debug errors in the application
|
||||
/// </summary>
|
||||
Debug,
|
||||
|
||||
/// <summary>
|
||||
/// Information to be able to keep track of state changes etc.
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// Something did not go as we expected, but it's no problem.
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// Something that should not fail failed, but we can still keep
|
||||
/// on going.
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Something failed, and we cannot handle it properly.
|
||||
/// </summary>
|
||||
Fatal
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface used to write to log files.
|
||||
/// </summary>
|
||||
public interface LogWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Write an entry to the log file.
|
||||
/// </summary>
|
||||
/// <param name="source">object that is writing to the log</param>
|
||||
/// <param name="priority">importance of the log message</param>
|
||||
/// <param name="message">the message</param>
|
||||
void Write(object source, LogPrio priority, string message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class writes to the console. It colors the output depending on the logprio and includes a 3-level stacktrace (in debug mode)
|
||||
/// </summary>
|
||||
/// <seealso cref="LogWriter"/>
|
||||
public sealed class ConsoleLogWriter : LogWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// The actual instance of this class.
|
||||
/// </summary>
|
||||
public static readonly ConsoleLogWriter Instance = new ConsoleLogWriter();
|
||||
|
||||
/// <summary>
|
||||
/// Logwriters the specified source.
|
||||
/// </summary>
|
||||
/// <param name="source">object that wrote the logentry.</param>
|
||||
/// <param name="prio">Importance of the log message</param>
|
||||
/// <param name="message">The message.</param>
|
||||
public void Write(object source, LogPrio prio, string message)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(DateTime.Now.ToString());
|
||||
sb.Append(" ");
|
||||
sb.Append(prio.ToString().PadRight(10));
|
||||
sb.Append(" | ");
|
||||
#if DEBUG
|
||||
StackTrace trace = new StackTrace();
|
||||
StackFrame[] frames = trace.GetFrames();
|
||||
int endFrame = frames.Length > 4 ? 4 : frames.Length;
|
||||
int startFrame = frames.Length > 0 ? 1 : 0;
|
||||
for (int i = startFrame; i < endFrame; ++i)
|
||||
{
|
||||
sb.Append(frames[i].GetMethod().Name);
|
||||
sb.Append(" -> ");
|
||||
}
|
||||
#else
|
||||
sb.Append(System.Reflection.MethodBase.GetCurrentMethod().Name);
|
||||
sb.Append(" | ");
|
||||
#endif
|
||||
sb.Append(message);
|
||||
|
||||
Console.ForegroundColor = GetColor(prio);
|
||||
Console.WriteLine(sb.ToString());
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get color for the specified logprio
|
||||
/// </summary>
|
||||
/// <param name="prio">prio for the log entry</param>
|
||||
/// <returns>A <see cref="ConsoleColor"/> for the prio</returns>
|
||||
public static ConsoleColor GetColor(LogPrio prio)
|
||||
{
|
||||
switch (prio)
|
||||
{
|
||||
case LogPrio.Trace:
|
||||
return ConsoleColor.DarkGray;
|
||||
case LogPrio.Debug:
|
||||
return ConsoleColor.Gray;
|
||||
case LogPrio.Info:
|
||||
return ConsoleColor.White;
|
||||
case LogPrio.Warning:
|
||||
return ConsoleColor.DarkMagenta;
|
||||
case LogPrio.Error:
|
||||
return ConsoleColor.Magenta;
|
||||
case LogPrio.Fatal:
|
||||
return ConsoleColor.Red;
|
||||
}
|
||||
|
||||
return ConsoleColor.Yellow;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default log writer, writes everything to null (nowhere).
|
||||
/// </summary>
|
||||
/// <seealso cref="LogWriter"/>
|
||||
public sealed class NullLogWriter : LogWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// The logging instance.
|
||||
/// </summary>
|
||||
public static readonly NullLogWriter Instance = new NullLogWriter();
|
||||
|
||||
/// <summary>
|
||||
/// Writes everything to null
|
||||
/// </summary>
|
||||
/// <param name="source">object that wrote the logentry.</param>
|
||||
/// <param name="prio">Importance of the log message</param>
|
||||
/// <param name="message">The message.</param>
|
||||
public void Write(object source, LogPrio prio, string message)
|
||||
{}
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains all HTTP Methods (according to the HTTP 1.1 specification)
|
||||
/// <para>
|
||||
/// See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class Method
|
||||
{
|
||||
/// <summary>
|
||||
/// The DELETE method requests that the origin server delete the resource identified by the Request-URI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method MAY be overridden by human intervention (or other means) on the origin server.
|
||||
/// The client cannot be guaranteed that the operation has been carried out, even if the status code
|
||||
/// returned from the origin server indicates that the action has been completed successfully.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// However, the server SHOULD NOT indicate success unless, at the time the response is given,
|
||||
/// it intends to delete the resource or move it to an inaccessible location.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A successful response SHOULD be 200 (OK) if the response includes an entity describing the status,
|
||||
/// 202 (Accepted) if the action has not yet been enacted,
|
||||
/// or 204 (No Content) if the action has been enacted but the response does not include an entity.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the request passes through a cache and the Request-URI identifies one or more currently cached entities,
|
||||
/// those entries SHOULD be treated as stale. Responses to this method are not cacheable.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public const string Delete = "DELETE";
|
||||
|
||||
/// <summary>
|
||||
/// The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the
|
||||
/// entity in the response and not the source text of the process, unless that text happens to be the output of the process.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The semantics of the GET method change to a "conditional GET" if the request message includes an
|
||||
/// If-Modified-Since, If-Unmodified-Since, If-Match, If-None-Match, or If-Range header field.
|
||||
/// A conditional GET method requests that the entity be transferred only under the circumstances described
|
||||
/// by the conditional header field(s). The conditional GET method is intended to reduce unnecessary network
|
||||
/// usage by allowing cached entities to be refreshed without requiring multiple requests or transferring
|
||||
/// data already held by the client.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public const string Get = "GET";
|
||||
|
||||
/// <summary>
|
||||
/// The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The meta information contained in the HTTP headers in response to a HEAD request SHOULD be identical to the
|
||||
/// information sent in response to a GET request. This method can be used for obtaining meta information about
|
||||
/// the entity implied by the request without transferring the entity-body itself.
|
||||
///
|
||||
/// This method is often used for testing hypertext links for validity, accessibility, and recent modification.
|
||||
/// </remarks>
|
||||
public const string Header = "HEAD";
|
||||
|
||||
/// <summary>
|
||||
/// <para>The OPTIONS method represents a request for information about the communication options available on the request/response chain identified by the Request-URI.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method allows the client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.</para>
|
||||
/// </remarks>
|
||||
public const string Options = "OPTIONS";
|
||||
|
||||
/// <summary>
|
||||
/// The POST method is used to request that the origin server accept the entity enclosed
|
||||
/// in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// POST is designed to allow a uniform method to cover the following functions:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// Annotation of existing resources;
|
||||
/// </item><item>
|
||||
/// Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles;
|
||||
/// </item><item>
|
||||
/// Providing a block of data, such as the result of submitting a form, to a data-handling process;
|
||||
/// </item><item>
|
||||
/// Extending a database through an append operation.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// If a resource has been created on the origin server, the response SHOULD be 201 (Created) and
|
||||
/// contain an entity which describes the status of the request and refers to the new resource, and a
|
||||
/// Location header (see section 14.30).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The action performed by the POST method might not result in a resource that can be identified by a URI.
|
||||
/// In this case, either 200 (OK) or 204 (No Content) is the appropriate response status, depending on
|
||||
/// whether or not the response includes an entity that describes the result.
|
||||
/// </para><para>
|
||||
/// Responses to this method are not cacheable, unless the response includes appropriate Cache-Control
|
||||
/// or Expires header fields. However, the 303 (See Other) response can be used to direct the user agent
|
||||
/// to retrieve a cacheable resource.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public const string Post = "POST";
|
||||
|
||||
/// <summary>
|
||||
/// The PUT method requests that the enclosed entity be stored under the supplied Request-URI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a
|
||||
/// modified version of the one residing on the origin server.
|
||||
/// </item><item>
|
||||
/// If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new
|
||||
/// resource by the requesting user agent, the origin server can create the resource with that URI.
|
||||
/// </item><item>
|
||||
/// If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response.
|
||||
/// </item><item>
|
||||
/// If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to
|
||||
/// indicate successful completion of the request.
|
||||
/// </item><item>
|
||||
/// If the resource could not be created or modified with the Request-URI, an appropriate error response SHOULD be
|
||||
/// given that reflects the nature of the problem.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// The recipient of the entity MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not
|
||||
/// understand or implement and MUST return a 501 (Not Implemented) response in such cases.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public const string Put = "PUT";
|
||||
|
||||
/// <summary>
|
||||
/// The TRACE method is used to invoke a remote, application-layer loop- back of the request message.
|
||||
/// </summary>
|
||||
public const string Trace = "TRACE";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains all HTTP Methods (according to the HTTP 1.1 specification)
|
||||
/// <para>
|
||||
/// See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Obsolete("Use Method.XXX instead.")]
|
||||
public static class Methods
|
||||
{
|
||||
/// <summary>
|
||||
/// The DELETE method requests that the origin server delete the resource identified by the Request-URI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method MAY be overridden by human intervention (or other means) on the origin server.
|
||||
/// The client cannot be guaranteed that the operation has been carried out, even if the status code
|
||||
/// returned from the origin server indicates that the action has been completed successfully.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// However, the server SHOULD NOT indicate success unless, at the time the response is given,
|
||||
/// it intends to delete the resource or move it to an inaccessible location.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A successful response SHOULD be 200 (OK) if the response includes an entity describing the status,
|
||||
/// 202 (Accepted) if the action has not yet been enacted,
|
||||
/// or 204 (No Content) if the action has been enacted but the response does not include an entity.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the request passes through a cache and the Request-URI identifies one or more currently cached entities,
|
||||
/// those entries SHOULD be treated as stale. Responses to this method are not cacheable.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public const string Delete = "DELETE";
|
||||
|
||||
/// <summary>
|
||||
/// The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the
|
||||
/// entity in the response and not the source text of the process, unless that text happens to be the output of the process.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The semantics of the GET method change to a "conditional GET" if the request message includes an
|
||||
/// If-Modified-Since, If-Unmodified-Since, If-Match, If-None-Match, or If-Range header field.
|
||||
/// A conditional GET method requests that the entity be transferred only under the circumstances described
|
||||
/// by the conditional header field(s). The conditional GET method is intended to reduce unnecessary network
|
||||
/// usage by allowing cached entities to be refreshed without requiring multiple requests or transferring
|
||||
/// data already held by the client.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public const string Get = "GET";
|
||||
|
||||
/// <summary>
|
||||
/// The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The meta information contained in the HTTP headers in response to a HEAD request SHOULD be identical to the
|
||||
/// information sent in response to a GET request. This method can be used for obtaining meta information about
|
||||
/// the entity implied by the request without transferring the entity-body itself.
|
||||
///
|
||||
/// This method is often used for testing hypertext links for validity, accessibility, and recent modification.
|
||||
/// </remarks>
|
||||
public const string Header = "HEAD";
|
||||
|
||||
/// <summary>
|
||||
/// <para>The OPTIONS method represents a request for information about the communication options available on the request/response chain identified by the Request-URI.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method allows the client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.</para>
|
||||
/// </remarks>
|
||||
public const string Options = "OPTIONS";
|
||||
|
||||
/// <summary>
|
||||
/// The POST method is used to request that the origin server accept the entity enclosed
|
||||
/// in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// POST is designed to allow a uniform method to cover the following functions:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// Annotation of existing resources;
|
||||
/// </item><item>
|
||||
/// Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles;
|
||||
/// </item><item>
|
||||
/// Providing a block of data, such as the result of submitting a form, to a data-handling process;
|
||||
/// </item><item>
|
||||
/// Extending a database through an append operation.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// If a resource has been created on the origin server, the response SHOULD be 201 (Created) and
|
||||
/// contain an entity which describes the status of the request and refers to the new resource, and a
|
||||
/// Location header (see section 14.30).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The action performed by the POST method might not result in a resource that can be identified by a URI.
|
||||
/// In this case, either 200 (OK) or 204 (No Content) is the appropriate response status, depending on
|
||||
/// whether or not the response includes an entity that describes the result.
|
||||
/// </para><para>
|
||||
/// Responses to this method are not cacheable, unless the response includes appropriate Cache-Control
|
||||
/// or Expires header fields. However, the 303 (See Other) response can be used to direct the user agent
|
||||
/// to retrieve a cacheable resource.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public const string Post = "POST";
|
||||
|
||||
/// <summary>
|
||||
/// The PUT method requests that the enclosed entity be stored under the supplied Request-URI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a
|
||||
/// modified version of the one residing on the origin server.
|
||||
/// </item><item>
|
||||
/// If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new
|
||||
/// resource by the requesting user agent, the origin server can create the resource with that URI.
|
||||
/// </item><item>
|
||||
/// If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response.
|
||||
/// </item><item>
|
||||
/// If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to
|
||||
/// indicate successful completion of the request.
|
||||
/// </item><item>
|
||||
/// If the resource could not be created or modified with the Request-URI, an appropriate error response SHOULD be
|
||||
/// given that reflects the nature of the problem.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// The recipient of the entity MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not
|
||||
/// understand or implement and MUST return a 501 (Not Implemented) response in such cases.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public const string Put = "PUT";
|
||||
|
||||
/// <summary>
|
||||
/// The TRACE method is used to invoke a remote, application-layer loop- back of the request message.
|
||||
/// </summary>
|
||||
public const string Trace = "TRACE";
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer.Parser
|
||||
{
|
||||
/// <summary>
|
||||
/// Arguments used when more body bytes have come.
|
||||
/// </summary>
|
||||
public class BodyEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BodyEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="buffer">buffer that contains the received bytes.</param>
|
||||
/// <param name="offset">offset in buffer where to start processing.</param>
|
||||
/// <param name="count">number of bytes from <paramref name="offset"/> that should be parsed.</param>
|
||||
public BodyEventArgs(byte[] buffer, int offset, int count)
|
||||
{
|
||||
Check.Require(buffer, "buffer");
|
||||
Check.Min(1, offset, "offset");
|
||||
Check.Min(1, count, "count");
|
||||
|
||||
Buffer = buffer;
|
||||
Offset = offset;
|
||||
Count = count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BodyEventArgs"/> class.
|
||||
/// </summary>
|
||||
public BodyEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets buffer that contains the received bytes.
|
||||
/// </summary>
|
||||
public byte[] Buffer { get; set; }
|
||||
/*
|
||||
/// <summary>
|
||||
/// Gets or sets number of bytes used by the request.
|
||||
/// </summary>
|
||||
public int BytesUsed { get; set; }
|
||||
*/
|
||||
/// <summary>
|
||||
/// Gets or sets number of bytes from <see cref="Offset"/> that should be parsed.
|
||||
/// </summary>
|
||||
public int Count { get; set; }
|
||||
/*
|
||||
/// <summary>
|
||||
/// Gets or sets whether the body is complete.
|
||||
/// </summary>
|
||||
public bool IsBodyComplete { get; set; }
|
||||
*/
|
||||
/// <summary>
|
||||
/// Gets or sets offset in buffer where to start processing.
|
||||
/// </summary>
|
||||
public int Offset { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer.Parser
|
||||
{
|
||||
/// <summary>
|
||||
/// Event arguments used when a new header have been parsed.
|
||||
/// </summary>
|
||||
public class HeaderEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HeaderEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of header.</param>
|
||||
/// <param name="value">Header value.</param>
|
||||
public HeaderEventArgs(string name, string value)
|
||||
{
|
||||
Check.NotEmpty(name, "name");
|
||||
Check.NotEmpty(value, "value");
|
||||
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HeaderEventArgs"/> class.
|
||||
/// </summary>
|
||||
public HeaderEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets header name.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets header value.
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,416 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using HttpServer.Exceptions;
|
||||
|
||||
namespace HttpServer.Parser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a HTTP request directly from a stream
|
||||
/// </summary>
|
||||
public class HttpRequestParser : IHttpRequestParser
|
||||
{
|
||||
private readonly BodyEventArgs _bodyArgs = new BodyEventArgs();
|
||||
private readonly HeaderEventArgs _headerArgs = new HeaderEventArgs();
|
||||
private ILogWriter _log;
|
||||
private readonly RequestLineEventArgs _requestLineArgs = new RequestLineEventArgs();
|
||||
private int _bodyBytesLeft;
|
||||
private string _curHeaderName = string.Empty;
|
||||
private string _curHeaderValue = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new request parser
|
||||
/// </summary>
|
||||
/// <param name="logWriter">delegate receiving log entries.</param>
|
||||
public HttpRequestParser(ILogWriter logWriter)
|
||||
{
|
||||
_log = logWriter ?? NullLogWriter.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a number of bytes to the body
|
||||
/// </summary>
|
||||
/// <param name="buffer">buffer containing more body bytes.</param>
|
||||
/// <param name="offset">starting offset in buffer</param>
|
||||
/// <param name="count">number of bytes, from offset, to read.</param>
|
||||
/// <returns>offset to continue from.</returns>
|
||||
private int AddToBody(byte[] buffer, int offset, int count)
|
||||
{
|
||||
// got all bytes we need, or just a few of them?
|
||||
int bytesUsed = count > _bodyBytesLeft ? _bodyBytesLeft : count;
|
||||
_bodyArgs.Buffer = buffer;
|
||||
_bodyArgs.Offset = offset;
|
||||
_bodyArgs.Count = bytesUsed;
|
||||
BodyBytesReceived(this, _bodyArgs);
|
||||
|
||||
_bodyBytesLeft -= bytesUsed;
|
||||
if (_bodyBytesLeft == 0)
|
||||
{
|
||||
// got a complete request.
|
||||
_log.Write(this, LogPrio.Trace, "Request parsed successfully.");
|
||||
OnRequestCompleted();
|
||||
Clear();
|
||||
}
|
||||
|
||||
return offset + bytesUsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all state information for the request.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_bodyBytesLeft = 0;
|
||||
_curHeaderName = string.Empty;
|
||||
_curHeaderValue = string.Empty;
|
||||
CurrentState = RequestParserState.FirstLine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the log writer.
|
||||
/// </summary>
|
||||
public ILogWriter LogWriter
|
||||
{
|
||||
get { return _log; }
|
||||
set { _log = value ?? NullLogWriter.Instance; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse request line
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <exception cref="BadRequestException">If line is incorrect</exception>
|
||||
/// <remarks>Expects the following format: "Method SP Request-URI SP HTTP-Version CRLF"</remarks>
|
||||
protected void OnFirstLine(string value)
|
||||
{
|
||||
//
|
||||
//todo: In the interest of robustness, servers SHOULD ignore any empty line(s) received where a Request-Line is expected.
|
||||
// In other words, if the server is reading the protocol stream at the beginning of a message and receives a CRLF first, it should ignore the CRLF.
|
||||
//
|
||||
_log.Write(this, LogPrio.Debug, "Got request: " + value);
|
||||
|
||||
//Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||
int pos = value.IndexOf(' ');
|
||||
if (pos == -1 || pos + 1 >= value.Length)
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "Invalid request line, missing Method. Line: " + value);
|
||||
throw new BadRequestException("Invalid request line, missing Method. Line: " + value);
|
||||
}
|
||||
|
||||
string method = value.Substring(0, pos).ToUpper();
|
||||
int oldPos = pos + 1;
|
||||
pos = value.IndexOf(' ', oldPos);
|
||||
if (pos == -1)
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "Invalid request line, missing URI. Line: " + value);
|
||||
throw new BadRequestException("Invalid request line, missing URI. Line: " + value);
|
||||
}
|
||||
string path = value.Substring(oldPos, pos - oldPos);
|
||||
if (path.Length > 4196)
|
||||
throw new BadRequestException("Too long URI.");
|
||||
|
||||
if (pos + 1 >= value.Length)
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "Invalid request line, missing HTTP-Version. Line: " + value);
|
||||
throw new BadRequestException("Invalid request line, missing HTTP-Version. Line: " + value);
|
||||
}
|
||||
string version = value.Substring(pos + 1);
|
||||
if (version.Length < 4 || string.Compare(version.Substring(0, 4), "HTTP", true) != 0)
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "Invalid HTTP version in request line. Line: " + value);
|
||||
throw new BadRequestException("Invalid HTTP version in Request line. Line: " + value);
|
||||
}
|
||||
|
||||
_requestLineArgs.HttpMethod = method;
|
||||
_requestLineArgs.HttpVersion = version;
|
||||
_requestLineArgs.UriPath = path;
|
||||
RequestLineReceived(this, _requestLineArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We've parsed a new header.
|
||||
/// </summary>
|
||||
/// <param name="name">Name in lower case</param>
|
||||
/// <param name="value">Value, unmodified.</param>
|
||||
/// <exception cref="BadRequestException">If content length cannot be parsed.</exception>
|
||||
protected void OnHeader(string name, string value)
|
||||
{
|
||||
_headerArgs.Name = name;
|
||||
_headerArgs.Value = value;
|
||||
if (string.Compare(name, "content-length", true) == 0)
|
||||
{
|
||||
if (!int.TryParse(value, out _bodyBytesLeft))
|
||||
throw new BadRequestException("Content length is not a number.");
|
||||
}
|
||||
|
||||
HeaderReceived(this, _headerArgs);
|
||||
}
|
||||
|
||||
private void OnRequestCompleted()
|
||||
{
|
||||
RequestCompleted(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#region IHttpRequestParser Members
|
||||
|
||||
/// <summary>
|
||||
/// Current state in parser.
|
||||
/// </summary>
|
||||
public RequestParserState CurrentState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parse a message
|
||||
/// </summary>
|
||||
/// <param name="buffer">bytes to parse.</param>
|
||||
/// <param name="offset">where in buffer that parsing should start</param>
|
||||
/// <param name="count">number of bytes to parse, starting on <paramref name="offset"/>.</param>
|
||||
/// <returns>offset (where to start parsing next).</returns>
|
||||
/// <exception cref="BadRequestException"><c>BadRequestException</c>.</exception>
|
||||
public int Parse(byte[] buffer, int offset, int count)
|
||||
{
|
||||
// add body bytes
|
||||
if (CurrentState == RequestParserState.Body)
|
||||
{
|
||||
return AddToBody(buffer, offset, count);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
string temp = Encoding.ASCII.GetString(buffer, offset, count);
|
||||
_log.Write(this, LogPrio.Trace, "\r\n\r\n HTTP MESSAGE: " + temp + "\r\n");
|
||||
#endif
|
||||
|
||||
int currentLine = 1;
|
||||
int startPos = -1;
|
||||
|
||||
// set start pos since this is from an partial request
|
||||
if (CurrentState == RequestParserState.HeaderValue)
|
||||
startPos = 0;
|
||||
|
||||
int endOfBufferPos = offset + count;
|
||||
|
||||
//<summary>
|
||||
// Handled bytes are used to keep track of the number of bytes processed.
|
||||
// We do this since we can handle partial requests (to be able to check headers and abort
|
||||
// invalid requests directly without having to process the whole header / body).
|
||||
// </summary>
|
||||
int handledBytes = 0;
|
||||
|
||||
for (int currentPos = offset; currentPos < endOfBufferPos; ++currentPos)
|
||||
{
|
||||
var ch = (char)buffer[currentPos];
|
||||
char nextCh = endOfBufferPos > currentPos + 1 ? (char)buffer[currentPos + 1] : char.MinValue;
|
||||
|
||||
if (ch == '\r')
|
||||
++currentLine;
|
||||
|
||||
switch (CurrentState)
|
||||
{
|
||||
case RequestParserState.FirstLine:
|
||||
if (currentPos == 8191)
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "HTTP Request is too large.");
|
||||
throw new BadRequestException("Too large request line.");
|
||||
}
|
||||
if (char.IsLetterOrDigit(ch) && startPos == -1)
|
||||
startPos = currentPos;
|
||||
if (startPos == -1 && (ch != '\r' || nextCh != '\n'))
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "Request line is not found.");
|
||||
throw new BadRequestException("Invalid request line.");
|
||||
}
|
||||
if (startPos != -1 && (ch == '\r' || ch == '\n'))
|
||||
{
|
||||
int size = GetLineBreakSize(buffer, currentPos);
|
||||
OnFirstLine(Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos));
|
||||
CurrentState = CurrentState + 1;
|
||||
currentPos += size - 1;
|
||||
handledBytes = currentPos + size - 1;
|
||||
startPos = -1;
|
||||
}
|
||||
break;
|
||||
case RequestParserState.HeaderName:
|
||||
if (ch == '\r' || ch == '\n')
|
||||
{
|
||||
currentPos += GetLineBreakSize(buffer, currentPos);
|
||||
if (_bodyBytesLeft == 0)
|
||||
{
|
||||
CurrentState = RequestParserState.FirstLine;
|
||||
_log.Write(this, LogPrio.Trace, "Request parsed successfully (no content).");
|
||||
OnRequestCompleted();
|
||||
Clear();
|
||||
return currentPos;
|
||||
}
|
||||
|
||||
CurrentState = RequestParserState.Body;
|
||||
if (currentPos + 1 < endOfBufferPos)
|
||||
{
|
||||
_log.Write(this, LogPrio.Trace, "Adding bytes to the body");
|
||||
return AddToBody(buffer, currentPos, endOfBufferPos - currentPos);
|
||||
}
|
||||
|
||||
return currentPos;
|
||||
}
|
||||
if (char.IsWhiteSpace(ch) || ch == ':')
|
||||
{
|
||||
if (startPos == -1)
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning,
|
||||
"Expected header name, got colon on line " + currentLine);
|
||||
throw new BadRequestException("Expected header name, got colon on line " + currentLine);
|
||||
}
|
||||
_curHeaderName = Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos);
|
||||
handledBytes = currentPos + 1;
|
||||
startPos = -1;
|
||||
CurrentState = CurrentState + 1;
|
||||
if (ch == ':')
|
||||
CurrentState = CurrentState + 1;
|
||||
}
|
||||
else if (startPos == -1)
|
||||
startPos = currentPos;
|
||||
else if (!char.IsLetterOrDigit(ch) && ch != '-')
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "Invalid character in header name on line " + currentLine);
|
||||
throw new BadRequestException("Invalid character in header name on line " + currentLine);
|
||||
}
|
||||
if (startPos != -1 && currentPos - startPos > 200)
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "Invalid header name on line " + currentLine);
|
||||
throw new BadRequestException("Invalid header name on line " + currentLine);
|
||||
}
|
||||
break;
|
||||
case RequestParserState.AfterName:
|
||||
if (ch == ':')
|
||||
{
|
||||
handledBytes = currentPos + 1;
|
||||
CurrentState = CurrentState + 1;
|
||||
}
|
||||
break;
|
||||
case RequestParserState.Between:
|
||||
{
|
||||
if (ch == ' ' || ch == '\t')
|
||||
continue;
|
||||
int newLineSize = GetLineBreakSize(buffer, currentPos);
|
||||
if (newLineSize > 0 && currentPos + newLineSize < endOfBufferPos &&
|
||||
char.IsWhiteSpace((char)buffer[currentPos + newLineSize]))
|
||||
{
|
||||
++currentPos;
|
||||
continue;
|
||||
}
|
||||
startPos = currentPos;
|
||||
CurrentState = CurrentState + 1;
|
||||
handledBytes = currentPos;
|
||||
continue;
|
||||
}
|
||||
case RequestParserState.HeaderValue:
|
||||
{
|
||||
if (ch != '\r' && ch != '\n')
|
||||
continue;
|
||||
int newLineSize = GetLineBreakSize(buffer, currentPos);
|
||||
if (startPos == -1)
|
||||
continue; // allow new lines before start of value
|
||||
|
||||
if (_curHeaderName == string.Empty)
|
||||
throw new BadRequestException("Missing header on line " + currentLine);
|
||||
if (startPos == -1)
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "Missing header value for '" + _curHeaderName);
|
||||
throw new BadRequestException("Missing header value for '" + _curHeaderName);
|
||||
}
|
||||
if (currentPos - startPos > 8190)
|
||||
{
|
||||
_log.Write(this, LogPrio.Warning, "Too large header value on line " + currentLine);
|
||||
throw new BadRequestException("Too large header value on line " + currentLine);
|
||||
}
|
||||
|
||||
// Header fields can be extended over multiple lines by preceding each extra line with at
|
||||
// least one SP or HT.
|
||||
if (endOfBufferPos > currentPos + newLineSize
|
||||
&& (buffer[currentPos + newLineSize] == ' ' || buffer[currentPos + newLineSize] == '\t'))
|
||||
{
|
||||
if (startPos != -1)
|
||||
_curHeaderValue = Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos);
|
||||
|
||||
_log.Write(this, LogPrio.Trace, "Header value is on multiple lines.");
|
||||
CurrentState = RequestParserState.Between;
|
||||
startPos = -1;
|
||||
currentPos += newLineSize - 1;
|
||||
handledBytes = currentPos + newLineSize - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
_curHeaderValue += Encoding.UTF8.GetString(buffer, startPos, currentPos - startPos);
|
||||
_log.Write(this, LogPrio.Trace, "Header [" + _curHeaderName + ": " + _curHeaderValue + "]");
|
||||
OnHeader(_curHeaderName, _curHeaderValue);
|
||||
|
||||
startPos = -1;
|
||||
CurrentState = RequestParserState.HeaderName;
|
||||
_curHeaderValue = string.Empty;
|
||||
_curHeaderName = string.Empty;
|
||||
++currentPos;
|
||||
handledBytes = currentPos + 1;
|
||||
|
||||
// Check if we got a colon so we can cut header name, or crlf for end of header.
|
||||
bool canContinue = false;
|
||||
for (int j = currentPos; j < endOfBufferPos; ++j)
|
||||
{
|
||||
if (buffer[j] != ':' && buffer[j] != '\r' && buffer[j] != '\n') continue;
|
||||
canContinue = true;
|
||||
break;
|
||||
}
|
||||
if (!canContinue)
|
||||
{
|
||||
_log.Write(this, LogPrio.Trace, "Cant continue, no colon.");
|
||||
return currentPos + 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return handledBytes;
|
||||
}
|
||||
|
||||
int GetLineBreakSize(byte[] buffer, int offset)
|
||||
{
|
||||
if (buffer[offset] == '\r')
|
||||
{
|
||||
if (buffer.Length > offset + 1 && buffer[offset + 1] == '\n')
|
||||
return 2;
|
||||
else
|
||||
throw new BadRequestException("Got invalid linefeed.");
|
||||
}
|
||||
else if (buffer[offset] == '\n')
|
||||
{
|
||||
if (buffer.Length == offset + 1)
|
||||
return 1; // linux line feed
|
||||
if (buffer[offset + 1] != '\r')
|
||||
return 1; // linux line feed
|
||||
else
|
||||
return 2; // win line feed
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A request have been successfully parsed.
|
||||
/// </summary>
|
||||
public event EventHandler RequestCompleted = delegate { };
|
||||
|
||||
/// <summary>
|
||||
/// More body bytes have been received.
|
||||
/// </summary>
|
||||
public event EventHandler<BodyEventArgs> BodyBytesReceived = delegate { };
|
||||
|
||||
/// <summary>
|
||||
/// Request line have been received.
|
||||
/// </summary>
|
||||
public event EventHandler<RequestLineEventArgs> RequestLineReceived = delegate { };
|
||||
|
||||
/// <summary>
|
||||
/// A header have been received.
|
||||
/// </summary>
|
||||
public event EventHandler<HeaderEventArgs> HeaderReceived = delegate { };
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer.Parser
|
||||
{
|
||||
/// <summary>
|
||||
/// Used when the request line have been successfully parsed.
|
||||
/// </summary>
|
||||
public class RequestLineEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestLineEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="httpMethod">The HTTP method.</param>
|
||||
/// <param name="uriPath">The URI path.</param>
|
||||
/// <param name="httpVersion">The HTTP version.</param>
|
||||
public RequestLineEventArgs(string httpMethod, string uriPath, string httpVersion)
|
||||
{
|
||||
HttpMethod = httpMethod;
|
||||
UriPath = uriPath;
|
||||
HttpVersion = httpVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestLineEventArgs"/> class.
|
||||
/// </summary>
|
||||
public RequestLineEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets http method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should be one of the methods declared in <see cref="Method"/>.
|
||||
/// </remarks>
|
||||
public string HttpMethod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the version of the HTTP protocol that the client want to use.
|
||||
/// </summary>
|
||||
public string HttpVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets requested URI path.
|
||||
/// </summary>
|
||||
public string UriPath { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using HttpServer.Parser;
|
||||
|
||||
namespace HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates request parsers when needed.
|
||||
/// </summary>
|
||||
public class RequestParserFactory : IRequestParserFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new request parser.
|
||||
/// </summary>
|
||||
/// <param name="logWriter">Used when logging should be enabled.</param>
|
||||
/// <returns>A new request parser.</returns>
|
||||
public IHttpRequestParser CreateParser(ILogWriter logWriter)
|
||||
{
|
||||
return new HttpRequestParser(logWriter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates request parsers when needed.
|
||||
/// </summary>
|
||||
public interface IRequestParserFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new request parser.
|
||||
/// </summary>
|
||||
/// <param name="logWriter">Used when logging should be enabled.</param>
|
||||
/// <returns>A new request parser.</returns>
|
||||
IHttpRequestParser CreateParser(ILogWriter logWriter);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
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("Opensim HttpServer")]
|
||||
[assembly: AssemblyDescription("Gauffin Telecom AB HttpServer modified for OpenSim")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Gauffin Telecom AB, OpenSimulator")]
|
||||
[assembly: AssemblyProduct("HttpServer")]
|
||||
[assembly: AssemblyCopyright("Copyright © Gauffin Telecom AB 2008, OpenSimulator")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("9c7f48af-1db2-42df-bdc8-84644a21fc65")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HttpServer.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information on where in the template the error occurred, and what the error was.
|
||||
/// </summary>
|
||||
public class CodeGeneratorException : Exception
|
||||
{
|
||||
private readonly int _lineNumber;
|
||||
private readonly string _line = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CodeGeneratorException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="lineNumber">Line that the error appeared on.</param>
|
||||
/// <param name="error">error description.</param>
|
||||
public CodeGeneratorException(int lineNumber, string error) : base(error)
|
||||
{
|
||||
_lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CodeGeneratorException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="lineNumber">Line that the error appeared on.</param>
|
||||
/// <param name="error">error description.</param>
|
||||
/// <param name="line">line contents.</param>
|
||||
public CodeGeneratorException(int lineNumber, string line, string error) : base(error + "\nLine: " + line)
|
||||
{
|
||||
_lineNumber = lineNumber;
|
||||
_line = line;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the actual line where the error originated
|
||||
/// </summary>
|
||||
public string Line
|
||||
{
|
||||
get { return _line; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Line number in template
|
||||
/// </summary>
|
||||
public int LineNumber
|
||||
{
|
||||
get { return _lineNumber; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Fadd;
|
||||
|
||||
namespace HttpServer.Rendering
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This template loader loads all templates from a folder on the hard drive.
|
||||
/// </summary>
|
||||
public class FileTemplateLoader : ITemplateLoader
|
||||
{
|
||||
private string _pathPrefix = "views\\";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileTemplateLoader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pathPrefix">A prefix that is prepended to all requested files.</param>
|
||||
/// <seealso cref="PathPrefix"/>
|
||||
public FileTemplateLoader(string pathPrefix)
|
||||
{
|
||||
Check.Require(pathPrefix, "pathPrefix");
|
||||
_pathPrefix = pathPrefix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileTemplateLoader"/> class.
|
||||
/// </summary>
|
||||
public FileTemplateLoader()
|
||||
{
|
||||
_pathPrefix = Directory.Exists("..\\..\\views\\") ? "..\\..\\views\\" : "views\\";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A prefix that is prepended to all requested files.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// <![CDATA[
|
||||
/// // will look after template in Environment.CurrentDirectory + "views\\<ControllerName>\\templateName.*"
|
||||
/// mgr.PathPrefix = "views\\";
|
||||
/// ]]>
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <remarks>PathPrefix may not be null, only string.Empty
|
||||
/// </remarks>
|
||||
public string PathPrefix
|
||||
{
|
||||
get { return _pathPrefix; }
|
||||
set
|
||||
{
|
||||
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
if (!string.IsNullOrEmpty(_pathPrefix) && _pathPrefix[_pathPrefix.Length - 1] != '\\')
|
||||
_pathPrefix = value + "\\";
|
||||
else
|
||||
_pathPrefix = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a template into a <see cref="TextReader"/> and return it.
|
||||
/// </summary>
|
||||
/// <param name="path">Relative path (and filename) to template.</param>
|
||||
/// <returns>
|
||||
/// a <see cref="TextReader"/> if file was found; otherwise null.
|
||||
/// </returns>
|
||||
public TextReader LoadTemplate(string path)
|
||||
{
|
||||
if(!File.Exists(_pathPrefix + path))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
Stream stream = File.OpenRead(_pathPrefix + path);
|
||||
TextReader reader = new StreamReader(stream);
|
||||
return reader;
|
||||
}
|
||||
catch (DirectoryNotFoundException err)
|
||||
{
|
||||
throw new FileNotFoundException("Directory not found for: " + path, err);
|
||||
}
|
||||
catch (PathTooLongException err)
|
||||
{
|
||||
throw new FileNotFoundException("Path too long: " + path, err);
|
||||
}
|
||||
catch (UnauthorizedAccessException err)
|
||||
{
|
||||
throw new UnauthorizedAccessException("Failed to access: " + path, err);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch all files from the resource that matches the specified arguments.
|
||||
/// </summary>
|
||||
/// <param name="path">Where the file should reside.</param>
|
||||
/// <param name="filename">Files to check</param>
|
||||
/// <returns>
|
||||
/// a list of files if found; or an empty array if no files are found.
|
||||
/// </returns>
|
||||
public string[] GetFiles(string path, string filename)
|
||||
{
|
||||
if(Directory.Exists(_pathPrefix + path))
|
||||
return Directory.GetFiles(_pathPrefix + path, filename);
|
||||
|
||||
return new string[]{};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not the loader has an instance of the file requested
|
||||
/// </summary>
|
||||
/// <param name="filename">The name of the template/file</param>
|
||||
/// <returns>True if the loader can provide the file</returns>
|
||||
public bool HasTemplate(string filename)
|
||||
{
|
||||
return File.Exists(_pathPrefix + filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check's whether a template should be reloaded or not.
|
||||
/// </summary>
|
||||
/// <param name="info">template information</param>
|
||||
/// <returns>
|
||||
/// true if template is OK; false if it do not exist or are old.
|
||||
/// </returns>
|
||||
public bool CheckTemplate(ITemplateInfo info)
|
||||
{
|
||||
if(HasTemplate(info.Filename))
|
||||
return File.GetLastWriteTime(_pathPrefix + info.Filename) <= info.CompiledWhen;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,457 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using HttpServer.Rendering.Haml.Nodes;
|
||||
using HttpServer.Rendering.Haml.Rules;
|
||||
|
||||
namespace HttpServer.Rendering.Haml
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates C#/HTML from HAML code.
|
||||
/// </summary>
|
||||
/// <remarks>HAML documentation: http://haml.hamptoncatlin.com/docs/rdoc/classes/Haml.html</remarks>
|
||||
public class HamlGenerator : ITemplateGenerator
|
||||
{
|
||||
private LineInfo _currentLine;
|
||||
private int _lineNo = -1;
|
||||
private LineInfo _mother;
|
||||
private Node _parentNode;
|
||||
private LineInfo _prevLine;
|
||||
private TextReader _reader;
|
||||
private readonly List<Rule> _rules = new List<Rule>();
|
||||
private readonly ILogWriter _log;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HamlGenerator"/> class.
|
||||
/// </summary>
|
||||
public HamlGenerator()
|
||||
{
|
||||
_rules.Add(new NewLineRule());
|
||||
_rules.Add(new AttributesRule());
|
||||
_log = NullLogWriter.Instance;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HamlGenerator"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logWriter">The log writer.</param>
|
||||
public HamlGenerator(ILogWriter logWriter)
|
||||
{
|
||||
_rules.Add(new NewLineRule());
|
||||
_rules.Add(new AttributesRule());
|
||||
_log = logWriter ?? NullLogWriter.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Property to retrieve the root node for the latest parsed document
|
||||
/// </summary>
|
||||
public Node RootNode
|
||||
{
|
||||
get { return _parentNode; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check and validate indentation
|
||||
/// </summary>
|
||||
/// <param name="line">line to check</param>
|
||||
/// <param name="ws">number of white spaces</param>
|
||||
/// <param name="intendation">number of indentations (2 white spaces = 1 intend, 1 tab = 1 intend)</param>
|
||||
protected static void CheckIntendation(LineInfo line, out int ws, out int intendation)
|
||||
{
|
||||
intendation = 0;
|
||||
ws = -1;
|
||||
|
||||
char prevUnusedCh = line.UnparsedData[0];
|
||||
if (prevUnusedCh == '\t')
|
||||
{
|
||||
++intendation;
|
||||
prevUnusedCh = char.MinValue;
|
||||
}
|
||||
else if (prevUnusedCh != ' ')
|
||||
{
|
||||
ws = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 1; i < line.UnparsedData.Length; ++i)
|
||||
{
|
||||
char ch = line.UnparsedData[i];
|
||||
|
||||
if (ch == ' ')
|
||||
{
|
||||
if (prevUnusedCh == '\t')
|
||||
{
|
||||
++intendation;
|
||||
prevUnusedCh = ' ';
|
||||
continue;
|
||||
}
|
||||
if (prevUnusedCh == ' ')
|
||||
{
|
||||
prevUnusedCh = char.MinValue;
|
||||
++intendation;
|
||||
continue;
|
||||
}
|
||||
|
||||
prevUnusedCh = ' ';
|
||||
}
|
||||
else if (ch == '\t')
|
||||
{
|
||||
if (prevUnusedCh == ' ')
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data,
|
||||
"Invalid intendation sequence: One space + one tab. Should either be one tab or two spaces.");
|
||||
if (prevUnusedCh == char.MinValue)
|
||||
{
|
||||
++intendation;
|
||||
prevUnusedCh = char.MinValue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (prevUnusedCh != char.MinValue)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data,
|
||||
"Invalid intendation at char " + i + ", expected a space.");
|
||||
|
||||
if (i == 1 && !char.IsWhiteSpace(line.UnparsedData[0]))
|
||||
ws = 0;
|
||||
else
|
||||
ws = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check indentation
|
||||
/// </summary>
|
||||
/// <param name="line">fills line with intend info</param>
|
||||
protected static void CheckIntendation(LineInfo line)
|
||||
{
|
||||
int ws, intendation;
|
||||
CheckIntendation(line, out ws, out intendation);
|
||||
if (ws == -1)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data,
|
||||
"Failed to find indentation on line #" + line.LineNumber);
|
||||
|
||||
line.Set(ws, intendation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// check if current line is a multi line
|
||||
/// </summary>
|
||||
/// <param name="prevLine">previous line</param>
|
||||
/// <param name="line">current line</param>
|
||||
protected void CheckMultiLine(LineInfo prevLine, LineInfo line)
|
||||
{
|
||||
if (prevLine != null && prevLine.UnfinishedRule != null)
|
||||
{
|
||||
if (prevLine.UnfinishedRule.IsMultiLine(line, true))
|
||||
{
|
||||
_log.Write(this, LogPrio.Trace, line.LineNumber + ": " + prevLine.UnfinishedRule.GetType().Name +
|
||||
" says that the next line should be appended.");
|
||||
line.AppendNextLine = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Rule rule in _rules)
|
||||
{
|
||||
if (rule.IsMultiLine(line, false))
|
||||
{
|
||||
_log.Write(this, LogPrio.Trace, line.LineNumber + ": " + rule.GetType().Name +
|
||||
" says that the next line should be appended.");
|
||||
line.AppendNextLine = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate HTML code from the template.
|
||||
/// Code is encapsulated in <% and <%=
|
||||
/// </summary>
|
||||
/// <param name="writer">A <see cref="TextWriter"/> that the generated code will be written to.</param>
|
||||
/// <exception cref="InvalidOperationException">If the template have not been parsed first.</exception>
|
||||
/// <exception cref="CodeGeneratorException">If template is incorrect</exception>
|
||||
public void GenerateHtml(TextWriter writer)
|
||||
{
|
||||
foreach (Node child in _parentNode.Children)
|
||||
writer.Write(child.ToHtml());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first word (letters and digits only) from the specified offset.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetWord(string data, int offset)
|
||||
{
|
||||
for (int i = offset; i < data.Length; ++i)
|
||||
{
|
||||
if (!char.IsLetterOrDigit(data[i]) && data[i] != '!')
|
||||
return data.Substring(offset, i - offset + 1);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check indentation / node placement
|
||||
/// </summary>
|
||||
protected void HandlePlacement()
|
||||
{
|
||||
// Check intendation so that we know where to place the line
|
||||
|
||||
// Larger intendation = child
|
||||
if (_currentLine.Intendation > _prevLine.Intendation)
|
||||
{
|
||||
if (_currentLine.Intendation != _prevLine.Intendation + 1)
|
||||
throw new CodeGeneratorException(_currentLine.LineNumber,
|
||||
"Too large indentation, " + (_currentLine.Intendation -
|
||||
_prevLine.Intendation) +
|
||||
" steps instead of 1.");
|
||||
|
||||
_currentLine.Parent = _prevLine;
|
||||
}
|
||||
// same intendation = same parent.
|
||||
else if (_currentLine.Intendation == _prevLine.Intendation)
|
||||
_currentLine.Parent = _prevLine.Parent;
|
||||
|
||||
// Node should be placed on a node up the chain.
|
||||
else
|
||||
{
|
||||
// go back until we find someone at the same level
|
||||
LineInfo sameLevelNode = _prevLine;
|
||||
while (sameLevelNode != null && sameLevelNode.Intendation > _currentLine.Intendation)
|
||||
sameLevelNode = sameLevelNode.Parent;
|
||||
|
||||
if (sameLevelNode == null)
|
||||
{
|
||||
if (_currentLine.Intendation > 0)
|
||||
throw new CodeGeneratorException(_currentLine.LineNumber, "Failed to find parent.");
|
||||
|
||||
_currentLine.Parent = _mother;
|
||||
}
|
||||
else
|
||||
_currentLine.Parent = sameLevelNode.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parse a node
|
||||
/// todo: improve doc
|
||||
/// </summary>
|
||||
/// <param name="theLine"></param>
|
||||
/// <param name="prototypes"></param>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="textNode"></param>
|
||||
protected static void ParseNode(LineInfo theLine, NodeList prototypes, Node parent, TextNode textNode)
|
||||
{
|
||||
Node curNode = null;
|
||||
int offset = 0;
|
||||
|
||||
// parse each part of a line
|
||||
while (offset <= theLine.Data.Length - 1)
|
||||
{
|
||||
Node node = prototypes.GetPrototype(GetWord(theLine.Data, offset), curNode == null) ?? textNode;
|
||||
node = node.Parse(prototypes, curNode, theLine, ref offset);
|
||||
|
||||
// first node on line, set it as current
|
||||
if (curNode == null)
|
||||
{
|
||||
curNode = node;
|
||||
curNode.LineInfo = theLine;
|
||||
parent.Children.AddLast(node);
|
||||
}
|
||||
else
|
||||
curNode.AddModifier(node); // append attributes etc.
|
||||
}
|
||||
|
||||
foreach (LineInfo child in theLine.Children)
|
||||
ParseNode(child, prototypes, curNode, textNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PreParse goes through the text add handles indentation
|
||||
/// and all multi line cases.
|
||||
/// </summary>
|
||||
/// <param name="reader">Reader containing the text</param>
|
||||
protected void PreParse(TextReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
throw new ArgumentNullException("reader");
|
||||
|
||||
// Read first line to be able to assign it to the mother.
|
||||
if (!ReadLine())
|
||||
throw new CodeGeneratorException(1, "No data.");
|
||||
|
||||
if (_currentLine.Intendation != 0)
|
||||
throw new CodeGeneratorException(1, "Invalid indentation, should be 0.");
|
||||
_currentLine.Parent = _mother;
|
||||
CheckIntendation(_currentLine);
|
||||
CheckMultiLine(_prevLine, _currentLine);
|
||||
|
||||
while (ReadLine())
|
||||
{
|
||||
if (_currentLine.UnparsedData.Length == 0)
|
||||
continue;
|
||||
|
||||
CheckIntendation(_currentLine);
|
||||
CheckMultiLine(_prevLine, _currentLine);
|
||||
if (_prevLine.AppendNextLine)
|
||||
{
|
||||
_prevLine.Append(_currentLine);
|
||||
if(_currentLine.AppendNextLine)
|
||||
_prevLine.AppendNextLine = true;
|
||||
_currentLine = _prevLine;
|
||||
continue;
|
||||
}
|
||||
|
||||
HandlePlacement();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// print the entire document
|
||||
/// </summary>
|
||||
public void PrintDocument()
|
||||
{
|
||||
PrintNode(_mother);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print line information to the console
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
public void PrintNode(LineInfo line)
|
||||
{
|
||||
_log.Write(this, LogPrio.Debug, Spaces(line.Intendation) + line.Data);
|
||||
foreach (LineInfo info in line.Children)
|
||||
PrintNode(info);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read next line from file
|
||||
/// </summary>
|
||||
/// <returns>true if line could be read; false if EOF.</returns>
|
||||
protected bool ReadLine()
|
||||
{
|
||||
string line = _reader.ReadLine();
|
||||
string trimmedLine = (line != null) ? line.Trim(new char[] {' ', '\t'}) : string.Empty;
|
||||
|
||||
while (line != null && (trimmedLine == string.Empty
|
||||
|| (trimmedLine.Length > 0 && trimmedLine[0] == '-' && trimmedLine[1] == '/')
|
||||
))
|
||||
{
|
||||
++_lineNo;
|
||||
line = _reader.ReadLine();
|
||||
trimmedLine = (line != null) ? line.Trim(new char[] {' ', '\t'}) : string.Empty;
|
||||
}
|
||||
if (line == null)
|
||||
return false;
|
||||
|
||||
++_lineNo;
|
||||
_prevLine = _currentLine;
|
||||
_currentLine = new LineInfo(_lineNo, line);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a string with spaces.
|
||||
/// </summary>
|
||||
/// <param name="count">number of spaces.</param>
|
||||
/// <returns>string of spaces.</returns>
|
||||
public string Spaces(int count)
|
||||
{
|
||||
return "".PadLeft(count);
|
||||
}
|
||||
|
||||
#region ITemplateGenerator Members
|
||||
|
||||
/// <summary>
|
||||
/// Parse a file and convert into to our own template object code.
|
||||
/// </summary>
|
||||
/// <param name="fullPath">Path and filename to a template</param>
|
||||
/// <exception cref="CodeGeneratorException">If something is incorrect in the template.</exception>
|
||||
/// <exception cref="FileNotFoundException"></exception>
|
||||
/// <exception cref="DirectoryNotFoundException"></exception>
|
||||
/// <exception cref="UnauthorizedAccessException"></exception>
|
||||
/// <exception cref="PathTooLongException"></exception>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public void Parse(string fullPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fullPath))
|
||||
throw new ArgumentException("Path must be specified.", "fullPath");
|
||||
|
||||
Stream stream = null;
|
||||
try
|
||||
{
|
||||
stream = File.OpenRead(fullPath);
|
||||
TextReader reader = new StreamReader(stream);
|
||||
Parse(reader);
|
||||
reader.Close();
|
||||
reader.Dispose();
|
||||
stream.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (stream != null)
|
||||
stream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a file and convert into to our own template object code.
|
||||
/// </summary>
|
||||
/// <param name="reader">A <see cref="TextReader"/> containing our template</param>
|
||||
/// <exception cref="CodeGeneratorException">If something is incorrect in the template.</exception>
|
||||
public void Parse(TextReader reader)
|
||||
{
|
||||
_lineNo = -1;
|
||||
_reader = reader;
|
||||
_mother = new LineInfo(-1, string.Empty);
|
||||
_prevLine = null;
|
||||
_currentLine = null;
|
||||
|
||||
PreParse(reader);
|
||||
|
||||
NodeList prototypes = new NodeList();
|
||||
prototypes.Add(new AttributeNode(null));
|
||||
prototypes.Add(new TagNode(null));
|
||||
prototypes.Add(new IdNode(null));
|
||||
prototypes.Add(new SilentCodeNode(null));
|
||||
prototypes.Add(new ClassNode(null));
|
||||
prototypes.Add(new DisplayCodeNode(null));
|
||||
prototypes.Add(new DocTypeTag(null, null));
|
||||
prototypes.Add(new PartialNode(null));
|
||||
TextNode textNode = new TextNode(null, "prototype");
|
||||
_parentNode = new TextNode(null, string.Empty);
|
||||
|
||||
foreach (LineInfo info in _mother.Children)
|
||||
ParseNode(info, prototypes, _parentNode, textNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate C# code from the template.
|
||||
/// </summary>
|
||||
/// <param name="writer">A <see cref="TextWriter"/> that the generated code will be written to.</param>
|
||||
/// <exception cref="InvalidOperationException">If the template have not been parsed first.</exception>
|
||||
/// <exception cref="CodeGeneratorException">If template is incorrect</exception>
|
||||
public void GenerateCode(TextWriter writer)
|
||||
{
|
||||
writer.Write("sb.Append(@\"");
|
||||
bool inString = true;
|
||||
foreach (Node child in _parentNode.Children)
|
||||
writer.Write(child.ToCode(ref inString));
|
||||
if (inString)
|
||||
writer.Write("\");");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using HttpServer.Rendering.Haml.Rules;
|
||||
|
||||
namespace HttpServer.Rendering.Haml
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains line text and state information about a line in a HAML template.
|
||||
/// </summary>
|
||||
public class LineInfo
|
||||
{
|
||||
private bool _appendNextLine;
|
||||
private readonly LinkedList<LineInfo> _children = new LinkedList<LineInfo>();
|
||||
private string _unparsedData;
|
||||
private readonly LinkedList<LineInfo> _extraLines = new LinkedList<LineInfo>();
|
||||
private int _intendation;
|
||||
private readonly int _lineNumber;
|
||||
private LineInfo _parent;
|
||||
private string _data;
|
||||
private Rule _unfinishedRule;
|
||||
private int _whiteSpaces;
|
||||
private bool _selfClosed ;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LineInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="lineNumber">The line number.</param>
|
||||
/// <param name="unparsedData">The unparsed data (line contents).</param>
|
||||
public LineInfo(int lineNumber, string unparsedData)
|
||||
{
|
||||
_lineNumber = lineNumber;
|
||||
_unparsedData = unparsedData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the next line should be appended to us (multi line)
|
||||
/// </summary>
|
||||
public bool AppendNextLine
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_appendNextLine || _unfinishedRule != null)
|
||||
return true;
|
||||
|
||||
foreach (LineInfo line in _extraLines)
|
||||
if (line.UnfinishedRule != null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == false)
|
||||
throw new ArgumentException("Can only set AppendNextLine to true, false is set internally.");
|
||||
_appendNextLine = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will check that all rule conditions have been met.
|
||||
/// Will also remove the rules if they are done.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
private bool CheckUnfinishedRule(LineInfo line)
|
||||
{
|
||||
if (_unfinishedRule != null)
|
||||
{
|
||||
// rule is done, remove it.
|
||||
// we can exit extra lines may have rules that are done too.
|
||||
if (!_unfinishedRule.IsMultiLine(line, false))
|
||||
_unfinishedRule = null;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
bool res = false;
|
||||
foreach (LineInfo subLine in _extraLines)
|
||||
{
|
||||
if (subLine.CheckUnfinishedRule(line))
|
||||
res = true;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do NOT add yourself using the Add methods of the linkedList.
|
||||
/// Parent property will add node.
|
||||
/// </summary>
|
||||
public LinkedList<LineInfo> Children
|
||||
{
|
||||
get { return _children; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Untouched line text
|
||||
/// </summary>
|
||||
public string UnparsedData
|
||||
{
|
||||
get { return _unparsedData; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Has one or more children (intented more that this one)
|
||||
/// </summary>
|
||||
public bool HasChildren
|
||||
{
|
||||
get { return _children.Count > 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of intends (two spaces = 1, one tab = 1)
|
||||
/// </summary>
|
||||
public int Intendation
|
||||
{
|
||||
get { return _intendation; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Line number
|
||||
/// </summary>
|
||||
public int LineNumber
|
||||
{
|
||||
get { return _lineNumber; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parent node (one level up in intendation)
|
||||
/// </summary>
|
||||
public LineInfo Parent
|
||||
{
|
||||
get { return _parent; }
|
||||
set
|
||||
{
|
||||
_parent = value;
|
||||
_parent.Children.AddLast(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All line data generated on one line
|
||||
/// </summary>
|
||||
public string Data
|
||||
{
|
||||
get { return _data; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IRule have not got all needed information yet,
|
||||
/// keep appending lines to this LineInfo until rule says that it's done.
|
||||
/// </summary>
|
||||
public Rule UnfinishedRule
|
||||
{
|
||||
get { return _unfinishedRule; }
|
||||
set { _unfinishedRule = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of whitespaces before actual entry beings.
|
||||
/// </summary>
|
||||
public int WhiteSpaces
|
||||
{
|
||||
get { return _whiteSpaces; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if node is selfclosed (i.e. <br />)
|
||||
/// </summary>
|
||||
public bool SelfClosed
|
||||
{
|
||||
get { return _selfClosed; }
|
||||
set { _selfClosed = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Append another line
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
public void Append(LineInfo line)
|
||||
{
|
||||
SetParsedData(_data + line.Data);
|
||||
_extraLines.AddLast(line);
|
||||
|
||||
if (CheckUnfinishedRule(this))
|
||||
_appendNextLine = true;
|
||||
else
|
||||
_appendNextLine = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parsed line contents (without whitespaces in the beginning)
|
||||
/// </summary>
|
||||
/// <param name="data">text contents</param>
|
||||
protected void SetParsedData(string data)
|
||||
{
|
||||
_data = data;
|
||||
if (_data[_data.Length - 1] == '/')
|
||||
_selfClosed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set intendation info to previously added line text.
|
||||
/// </summary>
|
||||
/// <param name="whiteSpaces"></param>
|
||||
/// <param name="intendation"></param>
|
||||
public void Set(int whiteSpaces, int intendation)
|
||||
{
|
||||
_whiteSpaces = whiteSpaces;
|
||||
_intendation = intendation;
|
||||
SetParsedData(_unparsedData.Substring(whiteSpaces));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign line text
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="whiteSpaces"></param>
|
||||
/// <param name="intendation"></param>
|
||||
public void Set(string line, int whiteSpaces, int intendation)
|
||||
{
|
||||
_unparsedData = line;
|
||||
Set(whiteSpaces, intendation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="count"></param>
|
||||
/// <exception cref="InvalidOperationException">If extra lines have been added.</exception>
|
||||
public void TrimRight(int count)
|
||||
{
|
||||
if (_extraLines.Count > 0)
|
||||
throw new InvalidOperationException("Have added other lines, cant trim.");
|
||||
if (_data.Length < count + 1 || _unparsedData.Length < count + 1)
|
||||
throw new InvalidOperationException("To little data left.");
|
||||
|
||||
_unparsedData = _unparsedData.Substring(0, _unparsedData.Length - count);
|
||||
SetParsedData(_data.Substring(0, _data.Length - count));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using HttpServer.Rendering.Haml.Nodes;
|
||||
|
||||
namespace HttpServer.Rendering.Haml
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of prototype nodes.
|
||||
/// </summary>
|
||||
public class NodeList
|
||||
{
|
||||
private readonly List<Node> _nodes = new List<Node>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates the node.
|
||||
/// </summary>
|
||||
/// <param name="word">node identifier.</param>
|
||||
/// <param name="parent">parent node.</param>
|
||||
/// <returns>created node if identifier was found; otherwise null.</returns>
|
||||
public Node CreateNode(string word, Node parent)
|
||||
{
|
||||
foreach (Node node in _nodes)
|
||||
{
|
||||
if (node.CanHandle(word, parent == null))
|
||||
return (Node) Activator.CreateInstance(node.GetType(), new object[]{parent});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="word"></param>
|
||||
/// <param name="firstNode">first node on line</param>
|
||||
/// <returns></returns>
|
||||
public Node GetPrototype(string word, bool firstNode)
|
||||
{
|
||||
foreach (Node node in _nodes)
|
||||
{
|
||||
if (node.CanHandle(word, firstNode))
|
||||
return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a prototype
|
||||
/// </summary>
|
||||
/// <param name="node">prototype node</param>
|
||||
public void Add(Node node)
|
||||
{
|
||||
//todo: Replace types.
|
||||
_nodes.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using HttpServer.Rendering;
|
||||
using HttpServer.Rendering.Haml.Nodes;
|
||||
using HttpServer.Rendering.Haml;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains HTML attributes.
|
||||
/// </summary>
|
||||
public class AttributeNode : ChildNode
|
||||
{
|
||||
/// <summary>
|
||||
/// A attribute
|
||||
/// </summary>
|
||||
public class Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// value is a simple word or quoted text
|
||||
/// </summary>
|
||||
public bool Simple;
|
||||
/// <summary>
|
||||
/// Name of the attribute
|
||||
/// </summary>
|
||||
public string Name;
|
||||
/// <summary>
|
||||
/// Value, can be a statement, variable or quoted text.
|
||||
/// </summary>
|
||||
public string Value;
|
||||
}
|
||||
private List<Attribute> _attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new node
|
||||
/// </summary>
|
||||
/// <param name="parent">parent node</param>
|
||||
/// <param name="col">collection of attributes</param>
|
||||
public AttributeNode(Node parent, List<Attribute> col)
|
||||
: base(parent)
|
||||
{
|
||||
_attributes = col;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// create an attribute node
|
||||
/// </summary>
|
||||
/// <param name="parent">parent node</param>
|
||||
public AttributeNode(Node parent) : base(parent)
|
||||
{
|
||||
_attributes = new List<Attribute>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an attribute
|
||||
/// </summary>
|
||||
/// <param name="name">name of the attribute (case sensitive)</param>
|
||||
/// <returns>attribute if found; otherwise null.</returns>
|
||||
public Attribute GetAttribute(string name)
|
||||
{
|
||||
foreach (Attribute attribute in _attributes)
|
||||
{
|
||||
if (attribute.Name == name)
|
||||
return attribute;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// html attributes
|
||||
/// </summary>
|
||||
public List<Attribute> Attributes
|
||||
{
|
||||
get { return _attributes; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse node contents add return a fresh node.
|
||||
/// </summary>
|
||||
/// <param name="prototypes">List containing all node types</param>
|
||||
/// <param name="parent">Node that this is a subnode to. Can be null</param>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="offset">Where to start the parsing. Should be set to where the next node should start parsing.</param>
|
||||
/// <returns>A node corresponding to the bla bla; null if parsing failed.</returns>
|
||||
/// <exception cref="CodeGeneratorException"></exception>
|
||||
public override Node Parse(NodeList prototypes, Node parent, LineInfo line, ref int offset)
|
||||
{
|
||||
if (line.Data[offset] != '{')
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Attribute cant handle info at char " + offset + 1);
|
||||
|
||||
int endPos = GetEndPos(offset, line.Data, '}');
|
||||
if (endPos == -1)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Failed to find end of attribute list: '" + line.UnparsedData + "'.");
|
||||
|
||||
List<Attribute> col = new List<Attribute>();
|
||||
string attributes = line.Data.Substring(offset + 1, endPos - offset - 1);
|
||||
ParseAttributes(line, attributes, col);
|
||||
offset = endPos + 1;
|
||||
|
||||
|
||||
AttributeNode node = (AttributeNode)prototypes.CreateNode("{", parent);
|
||||
node._attributes = col;
|
||||
return AddMe(prototypes, parent, line, node);
|
||||
}
|
||||
|
||||
private static void ParseAttributes(LineInfo line, string attributes, List<Attribute> col)
|
||||
{
|
||||
bool inQuote = false;
|
||||
int parenthisCount = 0;
|
||||
string name = null;
|
||||
int start = -1;
|
||||
int step = 0; //0 = start of name, 1 = end of name, 2 = equal sign, 3 = start of value, 4 = end of value, 5 = comma
|
||||
for (int i = 0; i < attributes.Length; ++i)
|
||||
{
|
||||
char ch = attributes[i];
|
||||
|
||||
if (ch == '"')
|
||||
{
|
||||
inQuote = !inQuote;
|
||||
if (inQuote && step == 3)
|
||||
{
|
||||
++step;
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (inQuote)
|
||||
continue;
|
||||
|
||||
if (ch == '(')
|
||||
++parenthisCount;
|
||||
if (ch == ')')
|
||||
--parenthisCount;
|
||||
if (parenthisCount > 0)
|
||||
continue;
|
||||
|
||||
// find start of name
|
||||
if (step == 0)
|
||||
{
|
||||
if (!char.IsWhiteSpace(ch))
|
||||
{
|
||||
start = i;
|
||||
++step;
|
||||
}
|
||||
}
|
||||
// find end of name
|
||||
else if (step == 1)
|
||||
{
|
||||
if (char.IsWhiteSpace(ch) || ch == '=')
|
||||
{
|
||||
name = attributes.Substring(start, i - start);
|
||||
start = -1;
|
||||
++step;
|
||||
}
|
||||
}
|
||||
|
||||
// find equal
|
||||
if (step == 2)
|
||||
{
|
||||
if (ch == '=')
|
||||
++step;
|
||||
continue;
|
||||
}
|
||||
|
||||
// start of value
|
||||
if (step == 3)
|
||||
{
|
||||
if (!char.IsWhiteSpace(ch))
|
||||
{
|
||||
start = i;
|
||||
++step;
|
||||
}
|
||||
}
|
||||
|
||||
// end of value
|
||||
else if (step == 4)
|
||||
{
|
||||
if (ch == ',')
|
||||
{
|
||||
AddAttribute(col, name, attributes.Substring(start, i - start).Trim());
|
||||
start = -1;
|
||||
++step;
|
||||
}
|
||||
}
|
||||
|
||||
// find comma
|
||||
if (step == 5)
|
||||
{
|
||||
if (ch == ',')
|
||||
step = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (step > 0 && step < 4)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Invalid attributes");
|
||||
|
||||
if (step == 4)
|
||||
AddAttribute(col, name, attributes.Substring(start, attributes.Length - start));
|
||||
}
|
||||
|
||||
private static void AddAttribute(List<Attribute> col, string name, string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return;
|
||||
|
||||
// need to end with a ", else parsing will fail.
|
||||
/*
|
||||
if (value[0] == '\"' && value[value.Length -1] != '\"')
|
||||
col.Add(name, value + "+ @\"\""); else
|
||||
{*/
|
||||
bool complex = false;
|
||||
bool inStr = false;
|
||||
for (int i = 0; i < value.Length; ++i )
|
||||
{
|
||||
if (value[i] == '"')
|
||||
{
|
||||
inStr = !inStr;
|
||||
continue;
|
||||
}
|
||||
if (!inStr)
|
||||
{
|
||||
complex = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//if (pos != -1)
|
||||
// value = value.Insert(pos, "@");
|
||||
|
||||
Attribute attr = new Attribute();
|
||||
attr.Simple = !complex;
|
||||
attr.Name = name;
|
||||
attr.Value = value;
|
||||
col.Add(attr);
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// determines if this node can handle the line (by checking the first word);
|
||||
/// </summary>
|
||||
/// <param name="word">Controller char (word)</param>
|
||||
/// <returns>true if text belongs to this node type</returns>
|
||||
/// <param name="firstNode">first node on line</param>
|
||||
public override bool CanHandle(string word, bool firstNode)
|
||||
{
|
||||
if (word.Length >= 1 && word[0] == '{' && !firstNode)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert node to HTML (with ASP-tags)
|
||||
/// </summary>
|
||||
/// <returns>HTML string</returns>
|
||||
public override string ToHtml()
|
||||
{
|
||||
StringBuilder attrs = new StringBuilder();
|
||||
for (int i = 0; i < Attributes.Count; ++i)
|
||||
{
|
||||
if (!Attributes[i].Simple)
|
||||
attrs.AppendFormat("{0}=<%= {1} %> ", Attributes[i].Name, Attributes[i].Value);
|
||||
else
|
||||
attrs.AppendFormat("{0}={1} ", Attributes[i].Name, Attributes[i].Value);
|
||||
}
|
||||
|
||||
return attrs.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the node to c# code
|
||||
/// </summary>
|
||||
/// <param name="inString">True if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">true if all subnodes fit on one line</param>
|
||||
/// <param name="smallEnoughIsDefaultValue">smallEnough is a default value, recalc it</param>
|
||||
/// <returns>c# code</returns>
|
||||
protected override string ToCode(ref bool inString, bool smallEnough, bool smallEnoughIsDefaultValue)
|
||||
{
|
||||
StringBuilder attrs = new StringBuilder();
|
||||
for (int i = 0; i < Attributes.Count; ++i)
|
||||
{
|
||||
if (!Attributes[i].Simple)
|
||||
attrs.AppendFormat("{0}=\"\"\"); sb.Append({1}); sb.Append(@\"\"\" ", Attributes[i].Name,
|
||||
Attributes[i].Value);
|
||||
else
|
||||
attrs.AppendFormat("{0}=\"{1}\" ", Attributes[i].Name, Attributes[i].Value);
|
||||
}
|
||||
|
||||
if (attrs.Length > 1)
|
||||
attrs.Length = attrs.Length - 1;
|
||||
return attrs.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using HttpServer.Rendering.Haml.Nodes;
|
||||
using HttpServer.Rendering.Haml;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Child nodes may not be the first node on a line
|
||||
/// </summary>
|
||||
public abstract class ChildNode : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Child nodes may not be the first node on a line
|
||||
/// </summary>
|
||||
/// <param name="parent">parent node</param>
|
||||
public ChildNode(Node parent) : base(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a DIV node and add's the specified node to it.
|
||||
/// </summary>
|
||||
/// <param name="prototypes">Contains all prototypes for each control char. used to instanciate new nodes.</param>
|
||||
/// <param name="parent">parent node</param>
|
||||
/// <param name="line">current line information</param>
|
||||
/// <param name="me">node to add to the DIV node</param>
|
||||
/// <returns>current node</returns>
|
||||
public Node AddMe(NodeList prototypes, Node parent, LineInfo line, Node me)
|
||||
{
|
||||
if (parent == null)
|
||||
{
|
||||
TagNode tag = (TagNode)prototypes.CreateNode("%", parent);
|
||||
tag.Name = "div";
|
||||
tag.LineInfo = line;
|
||||
tag.AddModifier(me);
|
||||
return tag;
|
||||
}
|
||||
|
||||
return me;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get endposition for this modifier.
|
||||
/// </summary>
|
||||
/// <param name="offset">where to start searching</param>
|
||||
/// <param name="line">contents of the current line</param>
|
||||
/// <param name="terminator">char that tells us that this is the end position</param>
|
||||
/// <returns>index on the current line</returns>
|
||||
protected int GetEndPos(int offset, string line, char terminator)
|
||||
{
|
||||
// find string to parse
|
||||
bool inQuote = false;
|
||||
for (int i = offset + 1; i < line.Length; ++i)
|
||||
{
|
||||
char ch = line[i];
|
||||
if (ch == '"')
|
||||
inQuote = !inQuote;
|
||||
else if (ch == terminator && !inQuote)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a plain text node
|
||||
/// </summary>
|
||||
public override bool IsTextNode
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get endposition for this modifier.
|
||||
/// </summary>
|
||||
/// <param name="offset">where to start searching</param>
|
||||
/// <param name="line">contents of the current line</param>
|
||||
/// <returns>index on the current line</returns>
|
||||
protected int GetEndPos(int offset, string line)
|
||||
{
|
||||
// find string to parse
|
||||
bool inQuote = false;
|
||||
for (int i = offset + 1; i < line.Length; ++i)
|
||||
{
|
||||
char ch = line[i];
|
||||
if (ch == '"')
|
||||
inQuote = !inQuote;
|
||||
else if (!char.IsLetterOrDigit(ch) && !inQuote && ch != '_' && !(ch == '[' || ch == ']'))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
using HttpServer.Rendering;
|
||||
using HttpServer.Rendering.Haml.Nodes;
|
||||
using HttpServer.Rendering.Haml;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a html class node.
|
||||
/// </summary>
|
||||
class ClassNode : ChildNode
|
||||
{
|
||||
private string _name;
|
||||
|
||||
public ClassNode(Node parent) : base(parent)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse node contents add return a fresh node.
|
||||
/// </summary>
|
||||
/// <param name="prototypes">List containing all node types</param>
|
||||
/// <param name="parent">Node that this is a subnode to. Can be null</param>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="offset">Where to start the parsing. Should be set to where the next node should start parsing.</param>
|
||||
/// <returns>A node corresponding to the bla bla; null if parsing failed.</returns>
|
||||
/// <exception cref="CodeGeneratorException"></exception>
|
||||
public override Node Parse(NodeList prototypes, Node parent, LineInfo line, ref int offset)
|
||||
{
|
||||
if (offset > line.Data.Length - 1)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Too little data");
|
||||
|
||||
int pos = GetEndPos(offset, line.Data);
|
||||
if (pos == -1)
|
||||
pos = line.Data.Length;
|
||||
|
||||
++offset;
|
||||
string name = line.Data.Substring(offset, pos - offset);
|
||||
offset = pos;
|
||||
|
||||
ClassNode node = (ClassNode) prototypes.CreateNode(".", parent);
|
||||
node._name = name;
|
||||
return AddMe(prototypes, parent, line, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// determines if this node can handle the line (by checking the first word);
|
||||
/// </summary>
|
||||
/// <param name="word">Controller char (word)</param>
|
||||
/// <returns>true if text belongs to this node type</returns>
|
||||
/// <param name="firstNode">first node on line</param>
|
||||
public override bool CanHandle(string word, bool firstNode)
|
||||
{
|
||||
return word.Length > 0 && word[0] == '.';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert node to HTML (with ASP-tags)
|
||||
/// </summary>
|
||||
/// <returns>HTML string</returns>
|
||||
public override string ToHtml()
|
||||
{
|
||||
return "class=\"" + _name + "\" ";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the node to c# code
|
||||
/// </summary>
|
||||
/// <param name="inString">True if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">true if all subnodes fit on one line</param>
|
||||
/// <param name="smallEnoughIsDefaultValue">smallEnough is a default value, recalc it</param>
|
||||
/// <returns>c# code</returns>
|
||||
protected override string ToCode(ref bool inString, bool smallEnough, bool smallEnoughIsDefaultValue)
|
||||
{
|
||||
return "class=\"\"" + _name + "\"\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
using System.Text;
|
||||
using HttpServer.Rendering;
|
||||
using HttpServer.Rendering.Haml.Nodes;
|
||||
using HttpServer.Rendering.Haml;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// The follow node allows users to display code in a haml document
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// #main Welcome =user.FirstName
|
||||
/// </example>
|
||||
public class DisplayCodeNode : ChildNode
|
||||
{
|
||||
private string _code;
|
||||
|
||||
/// <summary>
|
||||
/// Contains C# code that will be rendered into the view.
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent node</param>
|
||||
public DisplayCodeNode(Node parent) : base(parent)
|
||||
{}
|
||||
|
||||
/// <summary>
|
||||
/// Parse node contents add return a fresh node.
|
||||
/// </summary>
|
||||
/// <param name="prototypes">List containing all node types</param>
|
||||
/// <param name="parent">Node that this is a subnode to. Can be null</param>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="offset">Where to start the parsing. Should be set to where the next node should start parsing.</param>
|
||||
/// <returns>A node corresponding to the bla bla; null if parsing failed.</returns>
|
||||
/// <exception cref="CodeGeneratorException"></exception>
|
||||
public override Node Parse(NodeList prototypes, Node parent, LineInfo line, ref int offset)
|
||||
{
|
||||
if (offset >= line.Data.Length)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Too little data");
|
||||
|
||||
int pos = line.Data.Length;
|
||||
|
||||
++offset;
|
||||
string name = line.Data.Substring(offset, pos - offset);
|
||||
offset = pos;
|
||||
|
||||
string trimmedData = line.Data.Trim();
|
||||
if (trimmedData.Length > 0 && trimmedData[trimmedData.Length-1] == ';')
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Displayed code should not end with semicolon.");
|
||||
|
||||
DisplayCodeNode node = (DisplayCodeNode)prototypes.CreateNode("=", parent);
|
||||
node._code = name;
|
||||
if (parent == null)
|
||||
node.LineInfo = line;
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// determines if this node can handle the line (by checking the first word);
|
||||
/// </summary>
|
||||
/// <param name="word">Controller char (word)</param>
|
||||
/// <returns>true if text belongs to this node type</returns>
|
||||
/// <param name="firstNode">first node on line</param>
|
||||
public override bool CanHandle(string word, bool firstNode)
|
||||
{
|
||||
return word.Length > 0 && word[0] == '=';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if this is a textnode (containg plain text)
|
||||
/// </summary>
|
||||
public override bool IsTextNode
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate HTML for this node (with asp tags for code)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToHtml()
|
||||
{
|
||||
if (Parent == null || (Parent.Children.Last.Value != this && LineInfo == null))
|
||||
return string.Format("<%= {0} %>", _code);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
string intend = LineInfo == null ? string.Empty : string.Empty.PadLeft(LineInfo.Intendation, '\t');
|
||||
sb.Append(intend);
|
||||
sb.Append("<%=");
|
||||
sb.Append(_code);
|
||||
|
||||
if (Children.Count == 0)
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (Node node in Children)
|
||||
sb.Append(node.ToHtml());
|
||||
|
||||
sb.Append(intend);
|
||||
sb.AppendLine("%>");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// = is placed at the end of a tag definition, after class, id, and attribute declarations.
|
||||
/// It’s just a shortcut for inserting Ruby code into an element. It works the same as = without a tag:
|
||||
/// it inserts the result of the Ruby code into the template.
|
||||
/// However, if the result is short enough, it is displayed entirely on one line.
|
||||
/// </summary>
|
||||
/// <param name="inString">True if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">true if all subnodes fit on one line</param>
|
||||
/// <param name="smallEnoughIsDefaultValue">smallEnough is a default value, recalc it</param>
|
||||
/// <returns>c# code</returns>
|
||||
protected override string ToCode(ref bool inString, bool smallEnough, bool smallEnoughIsDefaultValue)
|
||||
{
|
||||
if (LineInfo == null)
|
||||
{
|
||||
if (inString)
|
||||
return string.Format("\");sb.Append({0});sb.Append(@\"", _code);
|
||||
else
|
||||
return "sb.Append(" + _code + ");";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
string intend = LineInfo == null ? string.Empty : string.Empty.PadLeft(LineInfo.Intendation, '\t');
|
||||
if (inString)
|
||||
{
|
||||
sb.Append("\");");
|
||||
inString = false;
|
||||
}
|
||||
|
||||
if (smallEnough)
|
||||
sb.Append("sb.Append(");
|
||||
else
|
||||
{
|
||||
sb.Append("sb.AppendLine(");
|
||||
sb.Append(intend);
|
||||
}
|
||||
|
||||
// on same line
|
||||
sb.Append(_code);
|
||||
sb.Append(");");
|
||||
|
||||
foreach (Node node in Children)
|
||||
sb.Append(node.ToCode(ref inString, smallEnough));
|
||||
|
||||
if (!smallEnough)
|
||||
sb.AppendLine();
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
bool IsLastNode(Node parent)
|
||||
{
|
||||
if (parent == null)
|
||||
return false;
|
||||
if (parent.ModifierCount == 0)
|
||||
return false;
|
||||
return parent.LastModifier == this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using HttpServer.Rendering.Haml;
|
||||
using HttpServer.Rendering.Haml.Nodes;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
internal class DocTypeTag : Node
|
||||
{
|
||||
private readonly string _docType;
|
||||
|
||||
public DocTypeTag(string docType, Node parent) : base(parent)
|
||||
{
|
||||
_docType = docType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Text nodes should be added as child.
|
||||
/// </summary>
|
||||
public override bool IsTextNode
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse node contents add return a fresh node.
|
||||
/// </summary>
|
||||
/// <param name="prototypes">List containing all node types</param>
|
||||
/// <param name="parent">Node that this is a subnode to. Can be null</param>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="offset">Where to start the parsing. Should be set to where the next node should start parsing.</param>
|
||||
/// <returns>A node corresponding to the bla bla; null if parsing failed.</returns>
|
||||
/// <exception cref="CodeGeneratorException"></exception>
|
||||
public override Node Parse(NodeList prototypes, Node parent, LineInfo line, ref int offset)
|
||||
{
|
||||
offset = line.Data.Length;
|
||||
return new DocTypeTag(
|
||||
@"<!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.0 Strict//EN"" ""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"">".Replace("\"", "\"\""),
|
||||
parent);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// determines if this node can handle the line (by checking the first word);
|
||||
/// </summary>
|
||||
/// <param name="word">Controller char (word)</param>
|
||||
/// <returns>true if text belongs to this node type</returns>
|
||||
/// <param name="firstNode">First node on line, used since some nodes cannot exist on their own on a line.</param>
|
||||
public override bool CanHandle(string word, bool firstNode)
|
||||
{
|
||||
return word.Length >= 3 && word.Substring(0, 3) == "!!!" && firstNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert node to HTML (with ASP-tags)
|
||||
/// </summary>
|
||||
/// <returns>HTML string</returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public override string ToHtml()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the node to c# code
|
||||
/// </summary>
|
||||
/// <param name="inString">True if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">true if all subnodes fit on one line</param>
|
||||
/// <param name="smallEnoughIsDefaultValue">smallEnough is a default value, recalc it</param>
|
||||
/// <returns>c# code</returns>
|
||||
protected override string ToCode(ref bool inString, bool smallEnough, bool smallEnoughIsDefaultValue)
|
||||
{
|
||||
return _docType + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
using HttpServer.Rendering;
|
||||
using HttpServer.Rendering.Haml.Nodes;
|
||||
using HttpServer.Rendering.Haml;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
class IdNode : ChildNode
|
||||
{
|
||||
private string _id;
|
||||
|
||||
public IdNode(Node parent) : base(parent)
|
||||
{}
|
||||
|
||||
/// <summary>
|
||||
/// Parse node contents add return a fresh node.
|
||||
/// </summary>
|
||||
/// <param name="prototypes">List containing all node types</param>
|
||||
/// <param name="parent">Node that this is a subnode to. Can be null</param>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="offset">Where to start the parsing. Should be set to where the next node should start parsing.</param>
|
||||
/// <returns>A node corresponding to the bla bla; null if parsing failed.</returns>
|
||||
/// <exception cref="CodeGeneratorException"></exception>
|
||||
public override Node Parse(NodeList prototypes, Node parent, LineInfo line, ref int offset)
|
||||
{
|
||||
if (line.Data[offset] != '#')
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Node is not an id node.");
|
||||
|
||||
int endPos = GetEndPos(offset, line.Data);
|
||||
if (endPos == -1)
|
||||
endPos = line.Data.Length;
|
||||
|
||||
++offset;
|
||||
string id = line.Data.Substring(offset, endPos - offset);
|
||||
offset = endPos;
|
||||
|
||||
IdNode node = (IdNode)prototypes.CreateNode("#", parent);
|
||||
node._id = id;
|
||||
return AddMe(prototypes, parent, line, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// determines if this node can handle the line (by checking the first word);
|
||||
/// </summary>
|
||||
/// <param name="word">Controller char (word)</param>
|
||||
/// <returns>true if text belongs to this node type</returns>
|
||||
/// <param name="firstNode">first node on line</param>
|
||||
public override bool CanHandle(string word, bool firstNode)
|
||||
{
|
||||
return word.Length > 0 && word[0] == '#';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert node to HTML (with ASP-tags)
|
||||
/// </summary>
|
||||
/// <returns>HTML string</returns>
|
||||
public override string ToHtml()
|
||||
{
|
||||
return "id=\"" + _id + "\" ";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the node to c# code
|
||||
/// </summary>
|
||||
/// <param name="inString">True if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">true if all subnodes fit on one line</param>
|
||||
/// <param name="smallEnoughIsDefaultValue">smallEnough is a default value, recalc it</param>
|
||||
/// <returns>c# code</returns>
|
||||
protected override string ToCode(ref bool inString, bool smallEnough, bool smallEnoughIsDefaultValue)
|
||||
{
|
||||
return "id=\"\"" + _id + "\"\" ";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using HttpServer.Rendering;
|
||||
using HttpServer.Rendering.Haml;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// A node represents a controller code and contents. For example a html tag, an attribute list or something else.
|
||||
/// </summary>
|
||||
public abstract class Node
|
||||
{
|
||||
private readonly LinkedList<Node> _children = new LinkedList<Node>();
|
||||
private readonly LinkedList<Node> _modifiers = new LinkedList<Node>();
|
||||
private LineInfo _lineInfo;
|
||||
private readonly Node _parent;
|
||||
|
||||
/// <summary>
|
||||
/// Add a new node
|
||||
/// </summary>
|
||||
/// <param name="parent">parent node.</param>
|
||||
public Node(Node parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the node is applicable after another node as text instead of being applied as a modifier
|
||||
/// </summary>
|
||||
public abstract bool IsTextNode
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Count our children and our childrens children and so on...
|
||||
/// </summary>
|
||||
public int AllChildrenCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = _children.Count;
|
||||
|
||||
foreach (Node node in _children)
|
||||
count += node.AllChildrenCount;
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All child nodes for this node.
|
||||
/// </summary>
|
||||
public LinkedList<Node> Children
|
||||
{
|
||||
get { return _children; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should only be set for each parent.
|
||||
/// </summary>
|
||||
public LineInfo LineInfo
|
||||
{
|
||||
get { return _lineInfo; }
|
||||
set { _lineInfo = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifiers are used to modify the first node on the line.
|
||||
/// Typical modifiers are attribute lists, class names and IDs.
|
||||
/// </summary>
|
||||
protected LinkedList<Node> Modifiers
|
||||
{
|
||||
get { return _modifiers; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifiers are used to modify the first node on the line.
|
||||
/// Typical modifiers are attribute lists, class names and IDs.
|
||||
/// </summary>
|
||||
/// <param name="node">Modifier node</param>
|
||||
public void AddModifier(Node node)
|
||||
{
|
||||
if (node.IsTextNode)
|
||||
_children.AddLast(node);
|
||||
else
|
||||
_modifiers.AddLast(node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Last modifier for this node
|
||||
/// </summary>
|
||||
/// <seealso cref="Modifiers"/>
|
||||
public Node LastModifier
|
||||
{
|
||||
get { return _modifiers.Last.Value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of modifiers for this line.
|
||||
/// </summary>
|
||||
public int ModifierCount
|
||||
{
|
||||
get { return _modifiers.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parent node.
|
||||
/// </summary>
|
||||
public Node Parent
|
||||
{
|
||||
get { return _parent; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse node contents add return a fresh node.
|
||||
/// </summary>
|
||||
/// <param name="prototypes">List containing all node types</param>
|
||||
/// <param name="parent">Node that this is a subnode to. Can be null</param>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="offset">Where to start the parsing. Should be set to where the next node should start parsing.</param>
|
||||
/// <returns>A node corresponding to the bla bla; null if parsing failed.</returns>
|
||||
/// <exception cref="CodeGeneratorException"></exception>
|
||||
public abstract Node Parse(NodeList prototypes, Node parent, LineInfo line, ref int offset);
|
||||
|
||||
/// <summary>
|
||||
/// determines if this node can handle the line (by checking the first word);
|
||||
/// </summary>
|
||||
/// <param name="word">Controller char (word)</param>
|
||||
/// <returns>true if text belongs to this node type</returns>
|
||||
/// <param name="firstNode">First node on line, used since some nodes cannot exist on their own on a line.</param>
|
||||
public abstract bool CanHandle(string word, bool firstNode);
|
||||
|
||||
/// <summary>
|
||||
/// Generate HTML code (with ASP tags)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract string ToHtml();
|
||||
|
||||
/// <summary>
|
||||
/// Converts the node to c# code
|
||||
/// </summary>
|
||||
/// <param name="inString">true if we are inside the internal stringbuilder</param>
|
||||
/// <returns>c# code</returns>
|
||||
public virtual string ToCode(ref bool inString)
|
||||
{
|
||||
return ToCode(ref inString, false, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the node to c# code
|
||||
/// </summary>
|
||||
/// <param name="inString">True if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">true if all subnodes fit on one line</param>
|
||||
/// <returns>c# code</returns>
|
||||
public virtual string ToCode(ref bool inString, bool smallEnough)
|
||||
{
|
||||
return ToCode(ref inString, smallEnough, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate C# code of the node
|
||||
/// </summary>
|
||||
/// <param name="inString">true if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">code is small enough to fit on one row.</param>
|
||||
/// <param name="smallEnoughIsDefaultValue">smallEnough is a default value, recalc it</param>
|
||||
/// <returns>c# code</returns>
|
||||
protected abstract string ToCode(ref bool inString, bool smallEnough, bool smallEnoughIsDefaultValue);
|
||||
|
||||
/// <summary>
|
||||
/// Get intendation level for this node.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected int GetIntendation()
|
||||
{
|
||||
if (LineInfo == null)
|
||||
{
|
||||
if (Parent == null)
|
||||
return 0;
|
||||
else
|
||||
return Parent.GetIntendation() + 1;
|
||||
}
|
||||
else
|
||||
return LineInfo.Intendation;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
using System.Text;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// A partial is a HAML template that is inserted into other HAML templates.
|
||||
/// </summary>
|
||||
public class PartialNode : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the page/controller target for the partial.
|
||||
/// The PartialNode should be written as follows:
|
||||
/// ex.
|
||||
///
|
||||
/// _"requestedpage"{parametername="parametervalue",parametername2=parameter2,parametername3=parameter3:typeof(parameter3type)}
|
||||
/// </summary>
|
||||
private string _target;
|
||||
|
||||
/// <summary>
|
||||
/// create a new partial node.
|
||||
/// </summary>
|
||||
/// <param name="parent">parent node</param>
|
||||
public PartialNode(Node parent) : base(parent)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a plain text node
|
||||
/// </summary>
|
||||
public override bool IsTextNode
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the page/controller target for the node
|
||||
/// </summary>
|
||||
public string Target
|
||||
{
|
||||
get { return _target; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if this node can handle the line (by checking the first word);
|
||||
/// </summary>
|
||||
/// <param name="word">Controller char (word)</param>
|
||||
/// <returns>True if text belongs to this node type</returns>
|
||||
/// <param name="firstNode">First node on line.</param>
|
||||
public override bool CanHandle(string word, bool firstNode)
|
||||
{
|
||||
if(firstNode == false)
|
||||
return false;
|
||||
|
||||
if (word.Length >= 1 && word[0] == '_')
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse node contents add return a fresh node.
|
||||
/// </summary>
|
||||
/// <param name="parent">Node that this is a subnode to. Can be null</param>
|
||||
/// <param name="prototypes">A list with node types</param>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="offset">Where to start the parsing. Will be set to where the next node should start parsing</param>
|
||||
/// <returns>A node corresponding to the bla bla; null if parsing failed.</returns>
|
||||
/// <exception cref="CodeGeneratorException"></exception>
|
||||
public override Node Parse(NodeList prototypes, Node parent, LineInfo line, ref int offset)
|
||||
{
|
||||
if (offset > line.Data.Length - 1)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Tried to parse after end of line");
|
||||
|
||||
if (line.Data[offset] != '_')
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Not a PartialNode");
|
||||
|
||||
// From the first " sign (offset + 2) find the next " sign
|
||||
int pos = -1;
|
||||
for (int i = offset + 2; i < line.Data.Length; ++i)
|
||||
{
|
||||
if (line.Data[i] == '\"')
|
||||
{
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos == -1)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "PartialNode does not contain an end paranthesis.");
|
||||
|
||||
// Cut out the data between the two above found " signs and then start processing the address
|
||||
// The address is converted from the format /example/example/ to \\example\\example.haml
|
||||
PartialNode node = (PartialNode)prototypes.CreateNode("_", parent);
|
||||
node._target = line.Data.Substring(offset + 2, pos - offset - 2);
|
||||
if (node._target[node._target.Length - 1] == '/')
|
||||
node._target = node._target.Substring(0, node._target.Length - 1);
|
||||
if (node._target[0] == '/')
|
||||
node._target = node._target.Substring(1);
|
||||
node._target = node._target.Replace("/", "\\\\");
|
||||
node._target += ".haml";
|
||||
|
||||
offset = pos + 1;
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the node to c# code
|
||||
/// </summary>
|
||||
/// <param name="inString">True if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">true if all subnodes fit on one line</param>
|
||||
/// <param name="smallEnoughIsDefaultValue">smallEnough is a default value, recalc it</param>
|
||||
/// <returns>c# code</returns>
|
||||
protected override string ToCode(ref bool inString, bool smallEnough, bool smallEnoughIsDefaultValue)
|
||||
{
|
||||
if(Children.Count > 0)
|
||||
ThrowError(" must not contain children.");
|
||||
|
||||
string code = GetCode();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
string intend = LineInfo == null ? string.Empty : string.Empty.PadLeft(LineInfo.Intendation, '\t');
|
||||
|
||||
if (inString)
|
||||
{
|
||||
sb.Append("\");");
|
||||
inString = false;
|
||||
}
|
||||
|
||||
sb.Append(intend);
|
||||
sb.Append("sb.Append(");
|
||||
sb.Append(code);
|
||||
sb.Append(");");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert node to HTML (with ASP-tags)
|
||||
/// </summary>
|
||||
/// <returns>HTML string</returns>
|
||||
public override string ToHtml()
|
||||
{
|
||||
string code = GetCode();
|
||||
|
||||
string intend = LineInfo == null ? string.Empty : string.Empty.PadLeft(LineInfo.Intendation, '\t');
|
||||
return string.Format("{1}<% {0} %>", intend, code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to retrieve the code for the partial request
|
||||
/// </summary>
|
||||
/// <returns>A string representing the code for invocating a render of the partial</returns>
|
||||
protected string GetCode()
|
||||
{
|
||||
string argumentString = string.Empty;
|
||||
if (Modifiers.Count == 1 && Modifiers.First.Value is AttributeNode)
|
||||
{
|
||||
foreach (AttributeNode.Attribute attrib in ((AttributeNode)Modifiers.First.Value).Attributes)
|
||||
{
|
||||
// Put the argument name within parenthesis
|
||||
argumentString += "\"" + attrib.Name + "\", ";
|
||||
|
||||
// If the value contains a colon the part after the colon is extracted and used as a typeinfo argument
|
||||
if (attrib.Value.Contains(":"))
|
||||
{
|
||||
string[] parts = attrib.Value.Split(':');
|
||||
if (parts.Length > 2)
|
||||
ThrowError(" must only contain one colon(:) per attribute to define type.");
|
||||
|
||||
argumentString += parts[0] + ", " + parts[1];
|
||||
}
|
||||
else
|
||||
argumentString += attrib.Value;
|
||||
|
||||
argumentString += ", ";
|
||||
}
|
||||
}
|
||||
else if (Modifiers.Count > 1)
|
||||
ThrowError(" can at most contain one AttributeNode.");
|
||||
else if (Modifiers.Count == 1 && !(Modifiers.First.Value is AttributeNode))
|
||||
ThrowError(" can only contain an AttributeNode.");
|
||||
|
||||
// Remove last comma if arguments were passed
|
||||
if (!string.IsNullOrEmpty(argumentString))
|
||||
argumentString = argumentString.Substring(0, argumentString.Length - 2);
|
||||
|
||||
return "hiddenTemplateManager.RenderPartial(\"" + _target + "\", args, new TemplateArguments(" + argumentString + "))";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throw an exception with predefined information
|
||||
/// </summary>
|
||||
/// <param name="reason">why the exception was thrown</param>
|
||||
protected void ThrowError(string reason)
|
||||
{
|
||||
if (LineInfo == null)
|
||||
throw new CodeGeneratorException(0, "PartialNode with target '" + _target + "'" + reason);
|
||||
|
||||
throw new CodeGeneratorException(LineInfo.LineNumber, LineInfo.Data, "PartialNode with target '" + _target + "'" + reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
using System.Text;
|
||||
using HttpServer.Rendering;
|
||||
using HttpServer.Rendering.Haml.Nodes;
|
||||
using HttpServer.Rendering.Haml;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// The ’-’ character makes the text following it into “silent” code: C# code that is evaluated, but not output.
|
||||
/// It is not recommended that you use this widely; almost all processing code and logic should be restricted to the Controller, Helpers, or partials.
|
||||
///
|
||||
/// For example
|
||||
/// <code>
|
||||
/// - string foo = "hello"
|
||||
/// - foo += " there"
|
||||
/// - foo += " you!"
|
||||
/// %p= foo
|
||||
/// </code>
|
||||
///
|
||||
/// Is compiled to
|
||||
/// <example>
|
||||
/// <p>
|
||||
/// hello there you!
|
||||
///</p>
|
||||
/// </example>
|
||||
/// </summary>
|
||||
internal class SilentCodeNode : ChildNode
|
||||
{
|
||||
private string _code = string.Empty;
|
||||
|
||||
public SilentCodeNode(Node parent) : base(parent)
|
||||
{}
|
||||
|
||||
/// <summary>
|
||||
/// determines if this node can handle the line (by checking the first word);
|
||||
/// </summary>
|
||||
/// <param name="word">Controller char (word)</param>
|
||||
/// <returns>true if text belongs to this node type</returns>
|
||||
/// <param name="firstNode">first node on line</param>
|
||||
public override bool CanHandle(string word, bool firstNode)
|
||||
{
|
||||
return word.Length > 0 && word[0] == '-';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse node contents add return a fresh node.
|
||||
/// </summary>
|
||||
/// <param name="prototypes">List containing all node types</param>
|
||||
/// <param name="parent">Node that this is a subnode to. Can be null</param>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="offset">Where to start the parsing. Should be set to where the next node should start parsing.</param>
|
||||
/// <returns>A node corresponding to the bla bla; null if parsing failed.</returns>
|
||||
/// <exception cref="CodeGeneratorException"></exception>
|
||||
public override Node Parse(NodeList prototypes, Node parent, LineInfo line, ref int offset)
|
||||
{
|
||||
if (offset > line.Data.Length)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Too little data");
|
||||
|
||||
int pos = line.Data.Length;
|
||||
++offset;
|
||||
string code = line.Data.Substring(offset, pos - offset);
|
||||
offset = pos;
|
||||
|
||||
SilentCodeNode node = (SilentCodeNode) prototypes.CreateNode("-", parent);
|
||||
node._code = code;
|
||||
if (parent != null)
|
||||
node.LineInfo = line;
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the node to c# code
|
||||
/// </summary>
|
||||
/// <param name="inString">True if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">true if all subnodes fit on one line</param>
|
||||
/// <param name="smallEnoughIsDefaultValue">smallEnough is a default value, recalc it</param>
|
||||
/// <returns>c# code</returns>
|
||||
protected override string ToCode(ref bool inString, bool smallEnough, bool smallEnoughIsDefaultValue)
|
||||
{
|
||||
|
||||
//if (LineInfo == null)
|
||||
if ((Parent != null && Parent.Children.Last != null && Parent.Children.Last.Value != this) && LineInfo == null)
|
||||
{
|
||||
if (inString)
|
||||
return string.Format("\"); {0} sb.Append(@\"", _code);
|
||||
else
|
||||
return _code;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
string intend = LineInfo == null ? string.Empty : string.Empty.PadLeft(LineInfo.Intendation, '\t');
|
||||
if (inString)
|
||||
{
|
||||
sb.Append("\");");
|
||||
inString = false;
|
||||
}
|
||||
|
||||
sb.Append(intend);
|
||||
sb.Append(_code);
|
||||
|
||||
if (AllChildrenCount > 0)
|
||||
{
|
||||
sb.Append("{");
|
||||
if (Modifiers.Count != 0)
|
||||
throw new CodeGeneratorException(LineInfo.LineNumber, LineInfo.Data, "Code tags should not have any modifiers.");
|
||||
|
||||
foreach (Node node in Children)
|
||||
sb.Append(node.ToCode(ref inString));
|
||||
|
||||
if (inString)
|
||||
{
|
||||
sb.Append("\");");
|
||||
inString = false;
|
||||
}
|
||||
|
||||
sb.Append("}" + intend);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert node to HTML (with ASP-tags)
|
||||
/// </summary>
|
||||
/// <returns>HTML string</returns>
|
||||
public override string ToHtml()
|
||||
{
|
||||
if (LineInfo == null)
|
||||
return string.Format("<% {0} %>", _code);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
string intend = LineInfo == null ? string.Empty : string.Empty.PadLeft(LineInfo.Intendation, '\t');
|
||||
sb.Append(intend);
|
||||
sb.Append("<%");
|
||||
sb.Append(_code);
|
||||
|
||||
if (Children.Count != 0)
|
||||
sb.AppendLine();
|
||||
|
||||
if (Modifiers.Count != 0)
|
||||
throw new CodeGeneratorException(LineInfo.LineNumber, LineInfo.Data, "Code tags should not have any modifiers.");
|
||||
|
||||
foreach (Node node in Children)
|
||||
sb.Append(node.ToHtml());
|
||||
|
||||
sb.Append(intend);
|
||||
sb.AppendLine("%>");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
using System.Text;
|
||||
using HttpServer.Rendering;
|
||||
using HttpServer.Rendering.Haml;
|
||||
using HttpServer.Rendering.Haml.Nodes;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a HTML tag.
|
||||
/// </summary>
|
||||
public class TagNode : Node
|
||||
{
|
||||
private string _name;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new HTML tag node.
|
||||
/// </summary>
|
||||
/// <param name="parent">parent node</param>
|
||||
public TagNode(Node parent) : base(parent)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a plain text node
|
||||
/// </summary>
|
||||
public override bool IsTextNode
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// tag name
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set {
|
||||
_name = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// determines if this node can handle the line (by checking the first word);
|
||||
/// </summary>
|
||||
/// <param name="word">Controller char (word)</param>
|
||||
/// <returns>true if text belongs to this node type</returns>
|
||||
/// <param name="firstNode">first node on line.</param>
|
||||
public override bool CanHandle(string word, bool firstNode)
|
||||
{
|
||||
if (word.Length >= 1 && word[0] == '%')
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse node contents add return a fresh node.
|
||||
/// </summary>
|
||||
/// <param name="parent">Node that this is a subnode to. Can be null</param>
|
||||
/// <param name="prototypes">A list with node types</param>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="offset">Where to start the parsing. Will be set to where the next node should start parsing</param>
|
||||
/// <returns>A node corresponding to the bla bla; null if parsing failed.</returns>
|
||||
/// <exception cref="CodeGeneratorException"></exception>
|
||||
public override Node Parse(NodeList prototypes, Node parent, LineInfo line, ref int offset)
|
||||
{
|
||||
if (offset > line.Data.Length - 1)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Tried to parse after end of line");
|
||||
|
||||
if (line.Data[offset] != '%')
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Not a tag node");
|
||||
|
||||
int pos = -1;
|
||||
for (int i = offset + 1; i < line.Data.Length; ++i)
|
||||
{
|
||||
if (!char.IsLetterOrDigit(line.Data[i]))
|
||||
{
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos == -1)
|
||||
pos = line.Data.Length;
|
||||
|
||||
TagNode node = (TagNode) prototypes.CreateNode("%", parent);
|
||||
node.Name = line.Data.Substring(offset + 1, pos - offset - 1);
|
||||
if (node._name == "br" || node._name == "input" || node._name == "style" || node._name == "img")
|
||||
{
|
||||
line.SelfClosed = true;
|
||||
node.LineInfo = line;
|
||||
node.LineInfo.SelfClosed = true;
|
||||
}
|
||||
|
||||
offset = pos;
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the node to c# code
|
||||
/// </summary>
|
||||
/// <param name="inString">True if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">true if all subnodes fit on one line</param>
|
||||
/// <param name="smallEnoughIsDefaultValue">smallEnough is a default value, recalc it</param>
|
||||
/// <returns>c# code</returns>
|
||||
protected override string ToCode(ref bool inString, bool smallEnough, bool smallEnoughIsDefaultValue)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
string intend = LineInfo == null ? string.Empty : string.Empty.PadLeft(LineInfo.Intendation, '\t');
|
||||
|
||||
bool tempStr = inString;
|
||||
bool childSmallEnough = Children.Count == 0 ||
|
||||
(Children.Count == 1 && Children.First.Value.ToCode(ref tempStr).Length < 60 && AllChildrenCount <= 2);
|
||||
/*smallEnough = AllChildrenCount < 1 ||
|
||||
(Children.Count <= 2 &&
|
||||
Children.First.Value.ToCode(ref tempStr).Length < 40);
|
||||
*/
|
||||
//smallEnough = false; // small enough needs a lot of love.
|
||||
if (!inString)
|
||||
{
|
||||
sb.Append("sb.Append(@\"");
|
||||
inString = true;
|
||||
}
|
||||
|
||||
if (!smallEnough || LineInfo.Parent.Data == null)
|
||||
sb.Append(intend);
|
||||
sb.Append("<");
|
||||
sb.Append(_name);
|
||||
|
||||
if (Modifiers.Count > 0)
|
||||
sb.Append(' ');
|
||||
|
||||
// = is placed at the end of a tag definition, after class, id, and attribute declarations.
|
||||
// It’s just a shortcut for inserting Ruby code into an element. It works the same as = without a tag:
|
||||
// it inserts the result of the Ruby code into the template.
|
||||
|
||||
foreach (Node node in Modifiers)
|
||||
sb.Append(node.ToCode(ref inString, smallEnough));
|
||||
|
||||
if (LineInfo.SelfClosed)
|
||||
{
|
||||
sb.Append("/>");
|
||||
if (!childSmallEnough)
|
||||
sb.AppendLine();
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
if (childSmallEnough)
|
||||
sb.Append(">");
|
||||
else
|
||||
sb.AppendLine(">");
|
||||
|
||||
foreach (Node node in Children)
|
||||
sb.Append(node.ToCode(ref inString, childSmallEnough));
|
||||
|
||||
if (!inString)
|
||||
{
|
||||
sb.Append("sb.Append(@\"");
|
||||
inString = true;
|
||||
}
|
||||
|
||||
if (!childSmallEnough)
|
||||
{
|
||||
// seems to be done by children
|
||||
//sb.AppendLine();
|
||||
// Quick fix for textareas (that'll otherwise include the indentation in the textarea content) until next haml parser is done
|
||||
if(_name != "textarea")
|
||||
sb.Append(intend);
|
||||
}
|
||||
|
||||
sb.Append("</");
|
||||
sb.Append(_name);
|
||||
sb.Append(">");
|
||||
if (!smallEnough || LineInfo.Parent.Data == null)
|
||||
sb.AppendLine();
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert node to HTML (with ASP-tags)
|
||||
/// </summary>
|
||||
/// <returns>HTML string</returns>
|
||||
public override string ToHtml()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
string intend = LineInfo == null ? string.Empty : string.Empty.PadLeft(LineInfo.Intendation, '\t');
|
||||
sb.Append(intend);
|
||||
sb.Append("<");
|
||||
sb.Append(_name);
|
||||
|
||||
if (Modifiers.Count != 0)
|
||||
sb.Append(' ');
|
||||
|
||||
TextNode textNode = null;
|
||||
foreach (Node node in Modifiers)
|
||||
{
|
||||
if (node.IsTextNode)
|
||||
textNode = node as TextNode;
|
||||
else
|
||||
sb.Append(node.ToHtml());
|
||||
}
|
||||
|
||||
if (LineInfo.SelfClosed)
|
||||
{
|
||||
sb.Append("/>");
|
||||
return sb.ToString();
|
||||
}
|
||||
else
|
||||
sb.Append(">");
|
||||
|
||||
if (textNode != null)
|
||||
sb.Append(textNode.ToHtml());
|
||||
|
||||
if (Children.Count != 0)
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (Node node in Children)
|
||||
sb.Append(node.ToHtml());
|
||||
|
||||
|
||||
sb.Append(intend);
|
||||
sb.Append("</");
|
||||
sb.Append(_name);
|
||||
sb.AppendLine(">");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using HttpServer.Rendering;
|
||||
using HttpServer.Rendering.Haml;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// A text only node.
|
||||
/// </summary>
|
||||
public class TextNode : Node
|
||||
{
|
||||
private string _text;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="parent">parent node</param>
|
||||
/// <param name="text">plain text</param>
|
||||
public TextNode(Node parent, string text) : base(parent)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The text.
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get { return _text; }
|
||||
set { _text = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is this a text node (containing plain text)?
|
||||
/// </summary>
|
||||
public override bool IsTextNode
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse node contents add return a fresh node.
|
||||
/// </summary>
|
||||
/// <param name="prototypes">List containing all node types</param>
|
||||
/// <param name="parent">Node that this is a subnode to. Can be null</param>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="offset">Where to start the parsing. Should be set to where the next node should start parsing.</param>
|
||||
/// <returns>A node corresponding to the bla bla; null if parsing failed.</returns>
|
||||
/// <exception cref="CodeGeneratorException"></exception>
|
||||
public override Node Parse(NodeList prototypes, Node parent, LineInfo line, ref int offset)
|
||||
{
|
||||
// text on tag rows are identified by a single space.
|
||||
if (parent != null && line.Data[offset] == ' ')
|
||||
++offset;
|
||||
|
||||
TextNode node = new TextNode(parent, line.Data.Substring(offset));
|
||||
if (parent == null)
|
||||
node.LineInfo = line;
|
||||
offset = line.Data.Length;
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// determines if this node can handle the line (by checking the first word);
|
||||
/// </summary>
|
||||
/// <param name="word">Controller char (word)</param>
|
||||
/// <returns>true if text belongs to this node type</returns>
|
||||
/// <param name="firstNode">true if this is the first node on the line.</param>
|
||||
public override bool CanHandle(string word, bool firstNode)
|
||||
{
|
||||
return word.Length > 0 && (char.IsWhiteSpace(word[0]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate HTML for this node.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToHtml()
|
||||
{
|
||||
// lineinfo = first node on line
|
||||
if (LineInfo != null)
|
||||
return string.Empty.PadLeft(GetIntendation(), '\t') + _text + Environment.NewLine;
|
||||
else
|
||||
return _text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the node to c# code
|
||||
/// </summary>
|
||||
/// <param name="inString">True if we are inside the internal stringbuilder</param>
|
||||
/// <param name="smallEnough">true if all subnodes fit on one line</param>
|
||||
/// <param name="smallEnoughIsDefaultValue">todo: add description</param>
|
||||
/// <returns>c# code</returns>
|
||||
protected override string ToCode(ref bool inString, bool smallEnough, bool smallEnoughIsDefaultValue)
|
||||
{
|
||||
int intendCount = GetIntendation();
|
||||
string intend = string.Empty.PadLeft(intendCount, '\t');
|
||||
string text = _text.Replace("\"", "\"\"");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (!inString)
|
||||
{
|
||||
sb.Append("sb.Append(@\"");
|
||||
inString = true;
|
||||
}
|
||||
|
||||
if (Children.Count > 0)
|
||||
{
|
||||
if (smallEnough)
|
||||
sb.Append(text);
|
||||
else
|
||||
{
|
||||
sb.Append(intend);
|
||||
sb.AppendLine(text);
|
||||
}
|
||||
|
||||
foreach (Node node in Children)
|
||||
sb.Append(node.ToCode(ref inString, smallEnough));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// lineinfo = first node on line
|
||||
if (LineInfo != null && !smallEnough)
|
||||
sb.AppendLine(intend + text);
|
||||
else
|
||||
sb.Append(text);
|
||||
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using System;
|
||||
using HttpServer.Rendering;
|
||||
|
||||
namespace HttpServer.Rendering.Haml.Rules
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains an (html) attribute list.
|
||||
/// </summary>
|
||||
public class AttributesRule : Rule
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if this node spans over multiple lines.
|
||||
/// </summary>
|
||||
/// <param name="line">contains line information (and text)</param>
|
||||
/// <param name="isContinued">true if the previous line was continued.</param>
|
||||
/// <returns>true if this line continues onto the next.</returns>
|
||||
public override bool IsMultiLine(LineInfo line, bool isContinued)
|
||||
{
|
||||
// hack to dont include code
|
||||
// a more proper way would have bene to scan after each tag
|
||||
if (!isContinued)
|
||||
{
|
||||
char ch = line.Data[0];
|
||||
if (ch != '#' && ch != '%' && ch != '.')
|
||||
return false;
|
||||
}
|
||||
|
||||
bool inQuote = false;
|
||||
bool inAttribute = false;
|
||||
if (isContinued && line.Data.IndexOf('{') == -1)
|
||||
inAttribute = true;
|
||||
foreach (char ch in line.Data)
|
||||
{
|
||||
if (ch == '"')
|
||||
inQuote = !inQuote;
|
||||
else if (ch == '{' && !inQuote)
|
||||
{
|
||||
if (inAttribute)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data,
|
||||
"Found another start of attributes, but no close tag. Have you forgot one '}'?");
|
||||
inAttribute = true;
|
||||
}
|
||||
else if (ch == '}' && !inQuote)
|
||||
inAttribute = false;
|
||||
}
|
||||
|
||||
if (inQuote)
|
||||
throw new CodeGeneratorException(line.LineNumber, line.Data, "Attribute quotes can not span over multiple lines.");
|
||||
|
||||
if (inAttribute)
|
||||
{
|
||||
//todo: Attach a log writer.
|
||||
//Console.WriteLine("Attribute is not closed, setting unfinished rule");
|
||||
line.UnfinishedRule = this;
|
||||
|
||||
line.Data.TrimEnd();
|
||||
if (line.Data.EndsWith("|"))
|
||||
line.TrimRight(1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return inAttribute;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
namespace HttpServer.Rendering.Haml.Rules
|
||||
{
|
||||
/// <summary>
|
||||
/// IRule that says that something :)
|
||||
/// </summary>
|
||||
public class NewLineRule : Rule
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if this node spans over multiple lines.
|
||||
/// </summary>
|
||||
/// <param name="line">contains line information (and text)</param>
|
||||
/// <param name="isContinued">true if the previous line was continued.</param>
|
||||
/// <returns>true if this line continues onto the next.</returns>
|
||||
public override bool IsMultiLine(LineInfo line, bool isContinued)
|
||||
{
|
||||
string trimmed = line.Data.TrimEnd();
|
||||
if (trimmed.Length == 0)
|
||||
return false;
|
||||
|
||||
if (trimmed.EndsWith("|"))
|
||||
{
|
||||
line.TrimRight(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace HttpServer.Rendering.Haml.Rules
|
||||
{
|
||||
/// <summary>
|
||||
/// Rules are used during preparse
|
||||
/// </summary>
|
||||
public abstract class Rule
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if this node spans over multiple lines.
|
||||
/// </summary>
|
||||
/// <param name="line">contains line information (and text)</param>
|
||||
/// <param name="isContinued">true if rule have previously inited a multiline.</param>
|
||||
/// <returns>true if this line continues onto the next.</returns>///
|
||||
public abstract bool IsMultiLine(LineInfo line, bool isContinued);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user