aspnetcore/src/Microsoft.AspNetCore.Owin/OwinFeatureCollection.cs

407 lines
14 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.WebSockets;
using System.Reflection;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Features.Authentication;
namespace Microsoft.AspNetCore.Owin
{
using SendFileFunc = Func<string, long, long?, CancellationToken, Task>;
public class OwinFeatureCollection :
IFeatureCollection,
IHttpRequestFeature,
IHttpResponseFeature,
IHttpConnectionFeature,
IHttpSendFileFeature,
ITlsConnectionFeature,
IHttpRequestIdentifierFeature,
IHttpRequestLifetimeFeature,
IHttpAuthenticationFeature,
IHttpWebSocketFeature,
IOwinEnvironmentFeature
{
public IDictionary<string, object> Environment { get; set; }
private bool _headersSent;
public OwinFeatureCollection(IDictionary<string, object> environment)
{
Environment = environment;
SupportsWebSockets = true;
var register = Prop<Action<Action<object>, object>>(OwinConstants.CommonKeys.OnSendingHeaders);
register?.Invoke(state =>
{
var collection = (OwinFeatureCollection)state;
collection._headersSent = true;
}, this);
}
T Prop<T>(string key)
{
object value;
if (Environment.TryGetValue(key, out value) && value is T)
{
return (T)value;
}
return default(T);
}
void Prop(string key, object value)
{
Environment[key] = value;
}
string IHttpRequestFeature.Protocol
{
get { return Prop<string>(OwinConstants.RequestProtocol); }
set { Prop(OwinConstants.RequestProtocol, value); }
}
string IHttpRequestFeature.Scheme
{
get { return Prop<string>(OwinConstants.RequestScheme); }
set { Prop(OwinConstants.RequestScheme, value); }
}
string IHttpRequestFeature.Method
{
get { return Prop<string>(OwinConstants.RequestMethod); }
set { Prop(OwinConstants.RequestMethod, value); }
}
string IHttpRequestFeature.PathBase
{
get { return Prop<string>(OwinConstants.RequestPathBase); }
set { Prop(OwinConstants.RequestPathBase, value); }
}
string IHttpRequestFeature.Path
{
get { return Prop<string>(OwinConstants.RequestPath); }
set { Prop(OwinConstants.RequestPath, value); }
}
string IHttpRequestFeature.QueryString
{
get { return Utilities.AddQuestionMark(Prop<string>(OwinConstants.RequestQueryString)); }
set { Prop(OwinConstants.RequestQueryString, Utilities.RemoveQuestionMark(value)); }
}
IHeaderDictionary IHttpRequestFeature.Headers
{
get { return Utilities.MakeHeaderDictionary(Prop<IDictionary<string, string[]>>(OwinConstants.RequestHeaders)); }
set { Prop(OwinConstants.RequestHeaders, Utilities.MakeDictionaryStringArray(value)); }
}
string IHttpRequestIdentifierFeature.TraceIdentifier
{
get { return Prop<string>(OwinConstants.RequestId); }
set { Prop(OwinConstants.RequestId, value); }
}
Stream IHttpRequestFeature.Body
{
get { return Prop<Stream>(OwinConstants.RequestBody); }
set { Prop(OwinConstants.RequestBody, value); }
}
int IHttpResponseFeature.StatusCode
{
get { return Prop<int>(OwinConstants.ResponseStatusCode); }
set { Prop(OwinConstants.ResponseStatusCode, value); }
}
string IHttpResponseFeature.ReasonPhrase
{
get { return Prop<string>(OwinConstants.ResponseReasonPhrase); }
set { Prop(OwinConstants.ResponseReasonPhrase, value); }
}
IHeaderDictionary IHttpResponseFeature.Headers
{
get { return Utilities.MakeHeaderDictionary(Prop<IDictionary<string, string[]>>(OwinConstants.ResponseHeaders)); }
set { Prop(OwinConstants.ResponseHeaders, Utilities.MakeDictionaryStringArray(value)); }
}
Stream IHttpResponseFeature.Body
{
get { return Prop<Stream>(OwinConstants.ResponseBody); }
set { Prop(OwinConstants.ResponseBody, value); }
}
bool IHttpResponseFeature.HasStarted
{
get { return _headersSent; }
}
void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)
{
var register = Prop<Action<Action<object>, object>>(OwinConstants.CommonKeys.OnSendingHeaders);
if (register == null)
{
throw new NotSupportedException(OwinConstants.CommonKeys.OnSendingHeaders);
}
// Need to block on the callback since we can't change the OWIN signature to be async
register(s => callback(s).GetAwaiter().GetResult(), state);
}
void IHttpResponseFeature.OnCompleted(Func<object, Task> callback, object state)
{
throw new NotSupportedException();
}
IPAddress IHttpConnectionFeature.RemoteIpAddress
{
get { return IPAddress.Parse(Prop<string>(OwinConstants.CommonKeys.RemoteIpAddress)); }
set { Prop(OwinConstants.CommonKeys.RemoteIpAddress, value.ToString()); }
}
IPAddress IHttpConnectionFeature.LocalIpAddress
{
get { return IPAddress.Parse(Prop<string>(OwinConstants.CommonKeys.LocalIpAddress)); }
set { Prop(OwinConstants.CommonKeys.LocalIpAddress, value.ToString()); }
}
int IHttpConnectionFeature.RemotePort
{
get { return int.Parse(Prop<string>(OwinConstants.CommonKeys.RemotePort)); }
set { Prop(OwinConstants.CommonKeys.RemotePort, value.ToString(CultureInfo.InvariantCulture)); }
}
int IHttpConnectionFeature.LocalPort
{
get { return int.Parse(Prop<string>(OwinConstants.CommonKeys.LocalPort)); }
set { Prop(OwinConstants.CommonKeys.LocalPort, value.ToString(CultureInfo.InvariantCulture)); }
}
string IHttpConnectionFeature.ConnectionId
{
get { return Prop<string>(OwinConstants.CommonKeys.ConnectionId); }
set { Prop(OwinConstants.CommonKeys.ConnectionId, value); }
}
private bool SupportsSendFile
{
get
{
object obj;
return Environment.TryGetValue(OwinConstants.SendFiles.SendAsync, out obj) && obj != null;
}
}
Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
{
object obj;
if (Environment.TryGetValue(OwinConstants.SendFiles.SendAsync, out obj))
{
var func = (SendFileFunc)obj;
return func(path, offset, length, cancellation);
}
throw new NotSupportedException(OwinConstants.SendFiles.SendAsync);
}
private bool SupportsClientCerts
{
get
{
object obj;
if (string.Equals("https", ((IHttpRequestFeature)this).Scheme, StringComparison.OrdinalIgnoreCase)
&& (Environment.TryGetValue(OwinConstants.CommonKeys.LoadClientCertAsync, out obj)
|| Environment.TryGetValue(OwinConstants.CommonKeys.ClientCertificate, out obj))
&& obj != null)
{
return true;
}
return false;
}
}
X509Certificate2 ITlsConnectionFeature.ClientCertificate
{
get { return Prop<X509Certificate2>(OwinConstants.CommonKeys.ClientCertificate); }
set { Prop(OwinConstants.CommonKeys.ClientCertificate, value); }
}
async Task<X509Certificate2> ITlsConnectionFeature.GetClientCertificateAsync(CancellationToken cancellationToken)
{
var loadAsync = Prop<Func<Task>>(OwinConstants.CommonKeys.LoadClientCertAsync);
if (loadAsync != null)
{
await loadAsync();
}
return Prop<X509Certificate2>(OwinConstants.CommonKeys.ClientCertificate);
}
CancellationToken IHttpRequestLifetimeFeature.RequestAborted
{
get { return Prop<CancellationToken>(OwinConstants.CallCancelled); }
set { Prop(OwinConstants.CallCancelled, value); }
}
void IHttpRequestLifetimeFeature.Abort()
{
throw new NotImplementedException();
}
ClaimsPrincipal IHttpAuthenticationFeature.User
{
get
{
return Prop<ClaimsPrincipal>(OwinConstants.RequestUser)
?? Utilities.MakeClaimsPrincipal(Prop<IPrincipal>(OwinConstants.Security.User));
}
set
{
Prop(OwinConstants.RequestUser, value);
Prop(OwinConstants.Security.User, value);
}
}
IAuthenticationHandler IHttpAuthenticationFeature.Handler { get; set; }
/// <summary>
/// Gets or sets if the underlying server supports WebSockets. This is enabled by default.
/// The value should be consistent across requests.
/// </summary>
public bool SupportsWebSockets { get; set; }
bool IHttpWebSocketFeature.IsWebSocketRequest
{
get
{
object obj;
return Environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out obj);
}
}
Task<WebSocket> IHttpWebSocketFeature.AcceptAsync(WebSocketAcceptContext context)
{
object obj;
if (!Environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out obj))
{
throw new NotSupportedException("WebSockets are not supported"); // TODO: LOC
}
var accept = (Func<WebSocketAcceptContext, Task<WebSocket>>)obj;
return accept(context);
}
// IFeatureCollection
public int Revision
{
get { return 0; } // Not modifiable
}
public bool IsReadOnly
{
get { return true; }
}
public object this[Type key]
{
get { return Get(key); }
set { throw new NotSupportedException(); }
}
private bool SupportsInterface(Type key)
{
// Does this type implement the requested interface?
if (key.GetTypeInfo().IsAssignableFrom(GetType().GetTypeInfo()))
{
// Check for conditional features
if (key == typeof(IHttpSendFileFeature))
{
return SupportsSendFile;
}
else if (key == typeof(ITlsConnectionFeature))
{
return SupportsClientCerts;
}
else if (key == typeof(IHttpWebSocketFeature))
{
return SupportsWebSockets;
}
// The rest of the features are always supported.
return true;
}
return false;
}
public object Get(Type key)
{
if (SupportsInterface(key))
{
return this;
}
return null;
}
public void Set(Type key, object value)
{
throw new NotSupportedException();
}
public TFeature Get<TFeature>()
{
return (TFeature)this[typeof(TFeature)];
}
public void Set<TFeature>(TFeature instance)
{
this[typeof(TFeature)] = instance;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
{
yield return new KeyValuePair<Type, object>(typeof(IHttpRequestFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IHttpResponseFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IHttpConnectionFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IHttpRequestIdentifierFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IHttpRequestLifetimeFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IHttpAuthenticationFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IOwinEnvironmentFeature), this);
// Check for conditional features
if (SupportsSendFile)
{
yield return new KeyValuePair<Type, object>(typeof(IHttpSendFileFeature), this);
}
if (SupportsClientCerts)
{
yield return new KeyValuePair<Type, object>(typeof(ITlsConnectionFeature), this);
}
if (SupportsWebSockets)
{
yield return new KeyValuePair<Type, object>(typeof(IHttpWebSocketFeature), this);
}
}
public void Dispose()
{
}
}
}