//
// System.Net.HttpListenerRequest
//
-// Author:
-// Gonzalo Paniagua Javier (gonzalo@novell.com)
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
+// Marek Safar (marek.safar@gmail.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2011-2012 Xamarin, Inc. (http://xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-#if NET_2_0 && SECURITY_DEP
+#if SECURITY_DEP
+
+extern alias MonoSecurity;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
+#if NET_4_0
+using System.Security.Authentication.ExtendedProtection;
+#endif
+#if NET_4_5
+using System.Threading.Tasks;
+#endif
+using MonoSecurity::Mono.Security.Protocol.Tls;
+
namespace System.Net {
public sealed class HttpListenerRequest
{
+#if NET_4_0
+ class Context : TransportContext
+ {
+ public override ChannelBinding GetChannelBinding (ChannelBindingKind kind)
+ {
+ throw new NotImplementedException ();
+ }
+ }
+#endif
+
string [] accept_types;
- int client_cert_error;
Encoding content_encoding;
long content_length;
bool cl_set;
WebHeaderCollection headers;
string method;
Stream input_stream;
- bool is_authenticated;
Version version;
NameValueCollection query_string; // check if null is ok, check if read-only, check case-sensitiveness
string raw_url;
- Guid identifier;
Uri url;
Uri referrer;
string [] user_languages;
- bool no_get_certificate;
HttpListenerContext context;
bool is_chunked;
+ bool ka_set;
+ bool keep_alive;
+ delegate X509Certificate2 GCCDelegate ();
+ GCCDelegate gcc_delegate;
+
static byte [] _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
internal HttpListenerRequest (HttpListenerContext context)
{
this.context = context;
headers = new WebHeaderCollection ();
- input_stream = Stream.Null;
+ version = HttpVersion.Version10;
}
static char [] separators = new char [] { ' ' };
-#if false
- static readonly string [] methods = new string [] { "GET", "POST", "HEAD",
- "PUT", "CONNECT", "MKCOL" };
-#endif
internal void SetRequestLine (string req)
{
string [] parts = req.Split (separators, 3);
int ic = (int) c;
if ((ic >= 'A' && ic <= 'Z') ||
- (ic >= 'a' && ic <= 'z') ||
(ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' &&
c != '<' && c != '>' && c != '@' && c != ',' && c != ';' &&
c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' &&
context.ErrorMessage = "(Invalid verb)";
return;
}
-
-#if false
- //
- // According to bug #80504 we should allow any verbs to go
- // through.
- //
- if (Array.IndexOf (methods, method) == -1) {
- context.ErrorMessage = "Invalid request line (verb).";
- return;
- }
-#endif
raw_url = parts [1];
if (parts [2].Length != 8 || !parts [2].StartsWith ("HTTP/")) {
void CreateQueryString (string query)
{
- query_string = new NameValueCollection ();
- if (query == null || query.Length == 0)
+ if (query == null || query.Length == 0) {
+ query_string = new NameValueCollection (1);
return;
+ }
+ query_string = new NameValueCollection ();
+ if (query [0] == '?')
+ query = query.Substring (1);
string [] components = query.Split ('&');
foreach (string kv in components) {
int pos = kv.IndexOf ('=');
internal void FinishInitialization ()
{
string host = UserHostName;
- if (version > HttpVersion.Version10 && (host == null || host == "")) {
+ if (version > HttpVersion.Version10 && (host == null || host.Length == 0)) {
context.ErrorMessage = "Invalid host name";
return;
}
- if (host == null || host == "")
+ string path;
+ Uri raw_uri = null;
+ if (Uri.MaybeUri (raw_url) && Uri.TryCreate (raw_url, UriKind.Absolute, out raw_uri))
+ path = raw_uri.PathAndQuery;
+ else
+ path = raw_url;
+
+ if ((host == null || host.Length == 0))
host = UserHostAddress;
+ if (raw_uri != null)
+ host = raw_uri.Host;
+
int colon = host.IndexOf (':');
if (colon >= 0)
host = host.Substring (0, colon);
string base_uri = String.Format ("{0}://{1}:{2}",
(IsSecureConnection) ? "https" : "http",
- host,
- LocalEndPoint.Port);
- try {
- url = new Uri (base_uri + raw_url);
- } catch {
- context.ErrorMessage = "Invalid url";
+ host, LocalEndPoint.Port);
+
+ if (!Uri.TryCreate (base_uri + path, UriKind.Absolute, out url)){
+ context.ErrorMessage = "Invalid url: " + base_uri + path;
return;
}
CreateQueryString (url.Query);
- if (method == "GET" || method == "HEAD")
- return;
-
- string t_encoding = null;
if (version >= HttpVersion.Version11) {
- t_encoding = Headers ["Transfer-Encoding"];
+ string t_encoding = Headers ["Transfer-Encoding"];
+ is_chunked = (t_encoding != null && String.Compare (t_encoding, "chunked", StringComparison.OrdinalIgnoreCase) == 0);
// 'identity' is not valid!
- if (t_encoding != null && t_encoding != "chunked") {
+ if (t_encoding != null && !is_chunked) {
context.Connection.SendError (null, 501);
return;
}
}
- bool is_chunked = (t_encoding == "chunked");
if (!is_chunked && !cl_set) {
- context.Connection.SendError (null, 411);
- return;
- }
-
- if (is_chunked || content_length > 0) {
- input_stream = context.Connection.GetRequestStream (is_chunked, content_length);
+ if (String.Compare (method, "POST", StringComparison.OrdinalIgnoreCase) == 0 ||
+ String.Compare (method, "PUT", StringComparison.OrdinalIgnoreCase) == 0) {
+ context.Connection.SendError (null, 411);
+ return;
+ }
}
- if (Headers ["Expect"] == "100-continue") {
+ if (String.Compare (Headers ["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) {
ResponseStream output = context.Connection.GetResponseStream ();
output.InternalWrite (_100continue, 0, _100continue.Length);
}
}
+ internal static string Unquote (String str) {
+ int start = str.IndexOf ('\"');
+ int end = str.LastIndexOf ('\"');
+ if (start >= 0 && end >=0)
+ str = str.Substring (start + 1, end - 1);
+ return str.Trim ();
+ }
+
internal void AddHeader (string header)
{
int colon = header.IndexOf (':');
if (colon == -1 || colon == 0) {
context.ErrorMessage = "Bad Request";
+ context.ErrorStatus = 400;
return;
}
case "accept-language":
user_languages = val.Split (','); // yes, only split with a ','
break;
- case "accept-types":
+ case "accept":
accept_types = val.Split (','); // yes, only split with a ','
break;
case "content-length":
referrer = new Uri ("http://someone.is.screwing.with.the.headers.com/");
}
break;
- //TODO: cookie headers
+ case "cookie":
+ if (cookies == null)
+ cookies = new CookieCollection();
+
+ string[] cookieStrings = val.Split(new char[] {',', ';'});
+ Cookie current = null;
+ int version = 0;
+ foreach (string cookieString in cookieStrings) {
+ string str = cookieString.Trim ();
+ if (str.Length == 0)
+ continue;
+ if (str.StartsWith ("$Version")) {
+ version = Int32.Parse (Unquote (str.Substring (str.IndexOf ('=') + 1)));
+ } else if (str.StartsWith ("$Path")) {
+ if (current != null)
+ current.Path = str.Substring (str.IndexOf ('=') + 1).Trim ();
+ } else if (str.StartsWith ("$Domain")) {
+ if (current != null)
+ current.Domain = str.Substring (str.IndexOf ('=') + 1).Trim ();
+ } else if (str.StartsWith ("$Port")) {
+ if (current != null)
+ current.Port = str.Substring (str.IndexOf ('=') + 1).Trim ();
+ } else {
+ if (current != null) {
+ cookies.Add (current);
+ }
+ current = new Cookie ();
+ int idx = str.IndexOf ('=');
+ if (idx > 0) {
+ current.Name = str.Substring (0, idx).Trim ();
+ current.Value = str.Substring (idx + 1).Trim ();
+ } else {
+ current.Name = str.Trim ();
+ current.Value = String.Empty;
+ }
+ current.Version = version;
+ }
+ }
+ if (current != null) {
+ cookies.Add (current);
+ }
+ break;
+ }
+ }
+
+ // returns true is the stream could be reused.
+ internal bool FlushInput ()
+ {
+ if (!HasEntityBody)
+ return true;
+
+ int length = 2048;
+ if (content_length > 0)
+ length = (int) Math.Min (content_length, (long) length);
+
+ byte [] bytes = new byte [length];
+ while (true) {
+ // TODO: test if MS has a timeout when doing this
+ try {
+ IAsyncResult ares = InputStream.BeginRead (bytes, 0, length, null, null);
+ if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne (1000))
+ return false;
+ if (InputStream.EndRead (ares) <= 0)
+ return true;
+ } catch {
+ return false;
+ }
}
}
public int ClientCertificateError {
get {
- if (no_get_certificate)
- throw new InvalidOperationException (
- "Call GetClientCertificate() before calling this method.");
- return client_cert_error;
+ HttpConnection cnc = context.Connection;
+ if (cnc.ClientCertificate == null)
+ throw new InvalidOperationException ("No client certificate");
+ int [] errors = cnc.ClientCertificateErrors;
+ if (errors != null && errors.Length > 0)
+ return errors [0];
+ return 0;
}
}
}
public bool HasEntityBody {
- get { return (method == "GET" || method == "HEAD" || content_length <= 0 || is_chunked); }
+ get { return (content_length > 0 || is_chunked); }
}
public NameValueCollection Headers {
}
public Stream InputStream {
- get { return input_stream; }
+ get {
+ if (input_stream == null) {
+ if (is_chunked || content_length > 0)
+ input_stream = context.Connection.GetRequestStream (is_chunked, content_length);
+ else
+ input_stream = Stream.Null;
+ }
+
+ return input_stream;
+ }
}
+ [MonoTODO ("Always returns false")]
public bool IsAuthenticated {
- get { return is_authenticated; }
+ get { return false; }
}
public bool IsLocal {
}
public bool KeepAlive {
- get { return false; }
+ get {
+ if (ka_set)
+ return keep_alive;
+
+ ka_set = true;
+ // 1. Connection header
+ // 2. Protocol (1.1 == keep-alive by default)
+ // 3. Keep-Alive header
+ string cnc = headers ["Connection"];
+ if (!String.IsNullOrEmpty (cnc)) {
+ keep_alive = (0 == String.Compare (cnc, "keep-alive", StringComparison.OrdinalIgnoreCase));
+ } else if (version == HttpVersion.Version11) {
+ keep_alive = true;
+ } else {
+ cnc = headers ["keep-alive"];
+ if (!String.IsNullOrEmpty (cnc))
+ keep_alive = (0 != String.Compare (cnc, "closed", StringComparison.OrdinalIgnoreCase));
+ }
+ return keep_alive;
+ }
}
public IPEndPoint LocalEndPoint {
get { return context.Connection.RemoteEndPoint; }
}
+ [MonoTODO ("Always returns Guid.Empty")]
public Guid RequestTraceIdentifier {
- get { return identifier; }
+ get { return Guid.Empty; }
}
public Uri Url {
get { return user_languages; }
}
- public IAsyncResult BeginGetClientCertificate (AsyncCallback requestCallback, Object state)
+ public IAsyncResult BeginGetClientCertificate (AsyncCallback requestCallback, object state)
{
- return null;
+ if (gcc_delegate == null)
+ gcc_delegate = new GCCDelegate (GetClientCertificate);
+ return gcc_delegate.BeginInvoke (requestCallback, state);
}
-#if SECURITY_DEP
+
public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult)
{
- return null;
- // set no_client_certificate once done.
+ if (asyncResult == null)
+ throw new ArgumentNullException ("asyncResult");
+
+ if (gcc_delegate == null)
+ throw new InvalidOperationException ();
+
+ return gcc_delegate.EndInvoke (asyncResult);
}
public X509Certificate2 GetClientCertificate ()
{
- // set no_client_certificate once done.
+ return context.Connection.ClientCertificate;
+ }
+
+#if NET_4_0
+ [MonoTODO]
+ public string ServiceName {
+ get {
+ return null;
+ }
+ }
+
+ public TransportContext TransportContext {
+ get {
+ return new Context ();
+ }
+ }
+#endif
+
+#if NET_4_5
+ [MonoTODO]
+ public bool IsWebSocketRequest {
+ get {
+ return false;
+ }
+ }
- // InvalidOp if call in progress.
- return null;
+ public Task<X509Certificate2> GetClientCertificateAsync ()
+ {
+ return Task<X509Certificate2>.Factory.FromAsync (BeginGetClientCertificate, EndGetClientCertificate, null);
}
#endif
}