diff options
Diffstat (limited to 'src/DotNetOpenAuth.Core')
87 files changed, 2064 insertions, 4182 deletions
diff --git a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuthSection.cs b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuthSection.cs index 7c03c48..cdcd670 100644 --- a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuthSection.cs +++ b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuthSection.cs @@ -7,7 +7,6 @@ namespace DotNetOpenAuth.Configuration { using System; using System.Configuration; - using System.Diagnostics.Contracts; using System.Web; using System.Web.Configuration; @@ -15,7 +14,6 @@ namespace DotNetOpenAuth.Configuration { /// Represents the section in the host's .config file that configures /// this library's settings. /// </summary> - [ContractVerification(true)] public class DotNetOpenAuthSection : ConfigurationSectionGroup { /// <summary> /// The name of the section under which this library's settings must be found. diff --git a/src/DotNetOpenAuth.Core/Configuration/HostNameElement.cs b/src/DotNetOpenAuth.Core/Configuration/HostNameElement.cs index b46ece9..5386314 100644 --- a/src/DotNetOpenAuth.Core/Configuration/HostNameElement.cs +++ b/src/DotNetOpenAuth.Core/Configuration/HostNameElement.cs @@ -6,12 +6,10 @@ namespace DotNetOpenAuth.Configuration { using System.Configuration; - using System.Diagnostics.Contracts; /// <summary> /// Represents the name of a single host or a regex pattern for host names. /// </summary> - [ContractVerification(true)] internal class HostNameElement : ConfigurationElement { /// <summary> /// Gets the name of the @name attribute. diff --git a/src/DotNetOpenAuth.Core/Configuration/HostNameOrRegexCollection.cs b/src/DotNetOpenAuth.Core/Configuration/HostNameOrRegexCollection.cs index f009ce5..8f537c3 100644 --- a/src/DotNetOpenAuth.Core/Configuration/HostNameOrRegexCollection.cs +++ b/src/DotNetOpenAuth.Core/Configuration/HostNameOrRegexCollection.cs @@ -7,13 +7,12 @@ namespace DotNetOpenAuth.Configuration { using System.Collections.Generic; using System.Configuration; - using System.Diagnostics.Contracts; using System.Text.RegularExpressions; + using Validation; /// <summary> /// Represents a collection of child elements that describe host names either as literal host names or regex patterns. /// </summary> - [ContractVerification(true)] internal class HostNameOrRegexCollection : ConfigurationElementCollection { /// <summary> /// Initializes a new instance of the <see cref="HostNameOrRegexCollection"/> class. @@ -63,7 +62,7 @@ namespace DotNetOpenAuth.Configuration { /// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. /// </returns> protected override object GetElementKey(ConfigurationElement element) { - Contract.Assume(element != null); // this should be Contract.Requires in base class. + Requires.NotNull(element, "element"); return ((HostNameElement)element).Name ?? string.Empty; } } diff --git a/src/DotNetOpenAuth.Core/Configuration/MessagingElement.cs b/src/DotNetOpenAuth.Core/Configuration/MessagingElement.cs index ff98d36..64dfba3 100644 --- a/src/DotNetOpenAuth.Core/Configuration/MessagingElement.cs +++ b/src/DotNetOpenAuth.Core/Configuration/MessagingElement.cs @@ -7,14 +7,12 @@ namespace DotNetOpenAuth.Configuration { using System; using System.Configuration; - using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; /// <summary> /// Represents the <messaging> element in the host's .config file. /// </summary> - [ContractVerification(true)] public class MessagingElement : ConfigurationSection { /// <summary> /// The name of the <webResourceUrlProvider> sub-element. @@ -75,7 +73,6 @@ namespace DotNetOpenAuth.Configuration { /// </summary> public static MessagingElement Configuration { get { - Contract.Ensures(Contract.Result<MessagingElement>() != null); return (MessagingElement)ConfigurationManager.GetSection(MessagingElementName) ?? new MessagingElement(); } } diff --git a/src/DotNetOpenAuth.Core/Configuration/ReportingElement.cs b/src/DotNetOpenAuth.Core/Configuration/ReportingElement.cs index 0af8205..f0184a6 100644 --- a/src/DotNetOpenAuth.Core/Configuration/ReportingElement.cs +++ b/src/DotNetOpenAuth.Core/Configuration/ReportingElement.cs @@ -8,7 +8,6 @@ namespace DotNetOpenAuth.Configuration { using System; using System.Collections.Generic; using System.Configuration; - using System.Diagnostics.Contracts; using System.Linq; using System.Text; @@ -76,7 +75,6 @@ namespace DotNetOpenAuth.Configuration { /// </summary> public static ReportingElement Configuration { get { - Contract.Ensures(Contract.Result<ReportingElement>() != null); return (ReportingElement)ConfigurationManager.GetSection(ReportingElementName) ?? new ReportingElement(); } } diff --git a/src/DotNetOpenAuth.Core/Configuration/TrustedProviderConfigurationCollection.cs b/src/DotNetOpenAuth.Core/Configuration/TrustedProviderConfigurationCollection.cs index 96f60bf..de70f64 100644 --- a/src/DotNetOpenAuth.Core/Configuration/TrustedProviderConfigurationCollection.cs +++ b/src/DotNetOpenAuth.Core/Configuration/TrustedProviderConfigurationCollection.cs @@ -9,7 +9,7 @@ namespace DotNetOpenAuth.Configuration { using System.Collections.Generic; using System.Configuration; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; + using Validation; /// <summary> /// A configuration collection of trusted OP Endpoints. diff --git a/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationCollection.cs b/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationCollection.cs index 3e72722..fa146a2 100644 --- a/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationCollection.cs +++ b/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationCollection.cs @@ -8,16 +8,15 @@ namespace DotNetOpenAuth.Configuration { using System; using System.Collections.Generic; using System.Configuration; - using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; + using Validation; /// <summary> /// A collection of <see cref="TypeConfigurationElement<T>"/>. /// </summary> /// <typeparam name="T">The type that all types specified in the elements must derive from.</typeparam> - [ContractVerification(true)] internal class TypeConfigurationCollection<T> : ConfigurationElementCollection where T : class { /// <summary> @@ -42,12 +41,14 @@ namespace DotNetOpenAuth.Configuration { /// Creates instances of all the types listed in the collection. /// </summary> /// <param name="allowInternals">if set to <c>true</c> then internal types may be instantiated.</param> - /// <returns>A sequence of instances generated from types in this collection. May be empty, but never null.</returns> - internal IEnumerable<T> CreateInstances(bool allowInternals) { - Contract.Ensures(Contract.Result<IEnumerable<T>>() != null); + /// <param name="hostFactories">The host factories.</param> + /// <returns> + /// A sequence of instances generated from types in this collection. May be empty, but never null. + /// </returns> + internal IEnumerable<T> CreateInstances(bool allowInternals, IHostFactories hostFactories) { return from element in this.Cast<TypeConfigurationElement<T>>() where !element.IsEmpty - select element.CreateInstance(default(T), allowInternals); + select element.CreateInstance(default(T), allowInternals, hostFactories); } /// <summary> @@ -68,7 +69,7 @@ namespace DotNetOpenAuth.Configuration { /// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. /// </returns> protected override object GetElementKey(ConfigurationElement element) { - Contract.Assume(element != null); // this should be Contract.Requires in base class. + Requires.NotNull(element, "element"); TypeConfigurationElement<T> typedElement = (TypeConfigurationElement<T>)element; return (!string.IsNullOrEmpty(typedElement.TypeName) ? typedElement.TypeName : typedElement.XamlSource) ?? string.Empty; } diff --git a/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationElement.cs b/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationElement.cs index edbb614..bcf199f 100644 --- a/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationElement.cs +++ b/src/DotNetOpenAuth.Core/Configuration/TypeConfigurationElement.cs @@ -8,15 +8,10 @@ namespace DotNetOpenAuth.Configuration { using System; using System.Configuration; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.IO; using System.Reflection; using System.Web; -#if CLR4 using System.Xaml; -#else - using System.Windows.Markup; -#endif using DotNetOpenAuth.Messaging; /// <summary> @@ -80,11 +75,12 @@ namespace DotNetOpenAuth.Configuration { /// Creates an instance of the type described in the .config file. /// </summary> /// <param name="defaultValue">The value to return if no type is given in the .config file.</param> - /// <returns>The newly instantiated type.</returns> - public T CreateInstance(T defaultValue) { - Contract.Ensures(Contract.Result<T>() != null || Contract.Result<T>() == defaultValue); - - return this.CreateInstance(defaultValue, false); + /// <param name="hostFactories">The host factories.</param> + /// <returns> + /// The newly instantiated type. + /// </returns> + public T CreateInstance(T defaultValue, IHostFactories hostFactories) { + return this.CreateInstance(defaultValue, false, hostFactories); } /// <summary> @@ -92,11 +88,13 @@ namespace DotNetOpenAuth.Configuration { /// </summary> /// <param name="defaultValue">The value to return if no type is given in the .config file.</param> /// <param name="allowInternals">if set to <c>true</c> then internal types may be instantiated.</param> - /// <returns>The newly instantiated type.</returns> + /// <param name="hostFactories">The host factories.</param> + /// <returns> + /// The newly instantiated type. + /// </returns> [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")] - public T CreateInstance(T defaultValue, bool allowInternals) { - Contract.Ensures(Contract.Result<T>() != null || Contract.Result<T>() == defaultValue); - + public T CreateInstance(T defaultValue, bool allowInternals, IHostFactories hostFactories) { + T instance; if (this.CustomType != null) { if (!allowInternals) { // Although .NET will usually prevent our instantiating non-public types, @@ -104,7 +102,7 @@ namespace DotNetOpenAuth.Configuration { // But we don't want the host site to be able to do this, so we check ourselves. ErrorUtilities.VerifyArgument((this.CustomType.Attributes & TypeAttributes.Public) != 0, Strings.ConfigurationTypeMustBePublic, this.CustomType.FullName); } - return (T)Activator.CreateInstance(this.CustomType); + instance = (T)Activator.CreateInstance(this.CustomType); } else if (!string.IsNullOrEmpty(this.XamlSource)) { string source = this.XamlSource; if (source.StartsWith("~/", StringComparison.Ordinal)) { @@ -112,11 +110,18 @@ namespace DotNetOpenAuth.Configuration { source = HttpContext.Current.Server.MapPath(source); } using (Stream xamlFile = File.OpenRead(source)) { - return CreateInstanceFromXaml(xamlFile); + instance = CreateInstanceFromXaml(xamlFile); } } else { - return defaultValue; + instance = defaultValue; } + + var requiresHostFactories = instance as IRequireHostFactories; + if (requiresHostFactories != null) { + requiresHostFactories.HostFactories = hostFactories; + } + + return instance; } /// <summary> @@ -132,12 +137,7 @@ namespace DotNetOpenAuth.Configuration { /// be present. /// </remarks> private static T CreateInstanceFromXaml(Stream xaml) { - Contract.Ensures(Contract.Result<T>() != null); -#if CLR4 return (T)XamlServices.Load(xaml); -#else - return (T)XamlReader.Load(xaml); -#endif } } } diff --git a/src/DotNetOpenAuth.Core/Configuration/UntrustedWebRequestElement.cs b/src/DotNetOpenAuth.Core/Configuration/UntrustedWebRequestElement.cs index b49452a..a16522a 100644 --- a/src/DotNetOpenAuth.Core/Configuration/UntrustedWebRequestElement.cs +++ b/src/DotNetOpenAuth.Core/Configuration/UntrustedWebRequestElement.cs @@ -7,7 +7,6 @@ namespace DotNetOpenAuth.Configuration { using System; using System.Configuration; - using System.Diagnostics.Contracts; /// <summary> /// Represents the section of a .config file where security policies regarding web requests diff --git a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj index dc47259..ebfd000 100644 --- a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj +++ b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj @@ -4,6 +4,7 @@ <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> </PropertyGroup> <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> <PropertyGroup> @@ -19,20 +20,26 @@ </PropertyGroup> <ItemGroup> <Compile Include="Assumes.cs" /> + <Compile Include="IHostFactories.cs" /> + <Compile Include="IRequireHostFactories.cs" /> + <Compile Include="MachineKeyUtil.cs" /> <Compile Include="Messaging\Base64WebEncoder.cs" /> <Compile Include="Messaging\Bindings\AsymmetricCryptoKeyStoreWrapper.cs" /> <Compile Include="Messaging\Bindings\CryptoKey.cs" /> <Compile Include="Messaging\Bindings\CryptoKeyCollisionException.cs" /> + <Compile Include="Messaging\Bindings\HardCodedKeyCryptoKeyStore.cs" /> <Compile Include="Messaging\Bindings\ICryptoKeyStore.cs" /> + <Compile Include="Messaging\Bindings\ICryptoKeyAndNonceStore.cs" /> <Compile Include="Messaging\Bindings\MemoryCryptoKeyStore.cs" /> <Compile Include="Messaging\BinaryDataBagFormatter.cs" /> - <Compile Include="Messaging\CachedDirectWebResponse.cs" /> - <Compile Include="Messaging\ChannelContract.cs" /> + <Compile Include="Messaging\Bindings\MemoryCryptoKeyAndNonceStore.cs" /> + <Compile Include="Messaging\HttpResponseMessageWithOriginal.cs" /> + <Compile Include="Messaging\MessageProtectionTasks.cs" /> + <Compile Include="Messaging\MultipartContentMember.cs" /> <Compile Include="Messaging\DataBagFormatterBase.cs" /> <Compile Include="Messaging\HmacAlgorithms.cs" /> <Compile Include="Messaging\HttpRequestHeaders.cs" /> <Compile Include="Messaging\IHttpDirectRequest.cs" /> - <Compile Include="Messaging\IHttpDirectRequestContract.cs" /> <Compile Include="Messaging\IHttpIndirectResponse.cs" /> <Compile Include="Messaging\IMessageOriginalPayload.cs" /> <Compile Include="Messaging\DirectWebRequestOptions.cs" /> @@ -40,23 +47,17 @@ <Compile Include="Messaging\HostErrorException.cs" /> <Compile Include="Messaging\IHttpDirectResponse.cs" /> <Compile Include="Messaging\IExtensionMessage.cs" /> - <Compile Include="Messaging\IHttpDirectResponseContract.cs" /> <Compile Include="Messaging\IMessage.cs" /> - <Compile Include="Messaging\IncomingWebResponse.cs" /> <Compile Include="Messaging\IDirectResponseProtocolMessage.cs" /> <Compile Include="Messaging\EmptyDictionary.cs" /> <Compile Include="Messaging\EmptyEnumerator.cs" /> <Compile Include="Messaging\EmptyList.cs" /> <Compile Include="Messaging\ErrorUtilities.cs" /> <Compile Include="Messaging\IMessageWithEvents.cs" /> - <Compile Include="Messaging\IncomingWebResponseContract.cs" /> <Compile Include="Messaging\IProtocolMessageWithExtensions.cs" /> <Compile Include="Messaging\InternalErrorException.cs" /> <Compile Include="Messaging\IStreamSerializingDataBag.cs" /> <Compile Include="Messaging\KeyedCollectionDelegate.cs" /> - <Compile Include="Messaging\MultipartPostPart.cs" /> - <Compile Include="Messaging\NetworkDirectWebResponse.cs" /> - <Compile Include="Messaging\OutgoingWebResponseActionResult.cs" /> <Compile Include="Messaging\ProtocolFaultResponseException.cs" /> <Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" /> <Compile Include="Messaging\Reflection\IMessagePartFormattingEncoder.cs" /> @@ -72,8 +73,7 @@ <Compile Include="Messaging\TimestampEncoder.cs" /> <Compile Include="Messaging\IMessageWithBinaryData.cs" /> <Compile Include="Messaging\ChannelEventArgs.cs" /> - <Compile Include="Messaging\Bindings\NonceMemoryStore.cs" /> - <Compile Include="Messaging\IDirectWebRequestHandler.cs" /> + <Compile Include="Messaging\Bindings\MemoryNonceStore.cs" /> <Compile Include="Messaging\Bindings\INonceStore.cs" /> <Compile Include="Messaging\Bindings\StandardReplayProtectionBindingElement.cs" /> <Compile Include="Messaging\MessagePartAttribute.cs" /> @@ -102,20 +102,18 @@ <Compile Include="Messaging\Reflection\MessageDictionary.cs" /> <Compile Include="Messaging\Reflection\MessagePart.cs" /> <Compile Include="Messaging\UnprotectedMessageException.cs" /> - <Compile Include="Messaging\OutgoingWebResponse.cs" /> <Compile Include="Messaging\IProtocolMessage.cs" /> <Compile Include="Messaging\HttpDeliveryMethods.cs" /> <Compile Include="Messaging\MessageTransport.cs" /> <Compile Include="Messaging\ProtocolException.cs" /> <Compile Include="Messaging\TimespanSecondsEncoder.cs" /> - <Compile Include="Messaging\UntrustedWebRequestHandler.cs" /> - <Compile Include="Messaging\StandardWebRequestHandler.cs" /> <Compile Include="Messaging\MessageReceivingEndpoint.cs" /> </ItemGroup> <ItemGroup> <None Include="Messaging\Bindings\Bindings.cd" /> <None Include="Messaging\Exceptions.cd" /> <None Include="Messaging\Messaging.cd" /> + <None Include="packages.config" /> </ItemGroup> <ItemGroup> <Compile Include="Configuration\DotNetOpenAuthSection.cs" /> @@ -132,12 +130,13 @@ <Compile Include="Logger.cs" /> <Compile Include="Loggers\ILog.cs" /> <Compile Include="Loggers\Log4NetLogger.cs" /> + <Compile Include="Loggers\NLogLogger.cs" /> <Compile Include="Loggers\NoOpLogger.cs" /> <Compile Include="Loggers\TraceLogger.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Messaging\ReadOnlyDictionary.cs" /> <Compile Include="Reporting.cs" /> - <Compile Include="Requires.cs" /> + <Compile Include="RequiresEx.cs" /> <Compile Include="Strings.Designer.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> @@ -170,11 +169,50 @@ <EmbeddedResource Include="Messaging\MessagingStrings.sr.resx" /> </ItemGroup> <ItemGroup> - <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL"> + <Reference Include="log4net, Version=1.2.11.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\log4net.2.0.0\lib\net40-full\log4net.dll</HintPath> + </Reference> + <Reference Include="NLog"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <Private>True</Private> + <HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath> + </Reference> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Net.Http.WebRequest" /> + <Reference Include="System.Web.Helpers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <Private>True</Private> + <HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.Helpers.dll</HintPath> + </Reference> + <Reference Include="System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <Private>True</Private> + <HintPath>..\packages\Microsoft.AspNet.Mvc.4.0.20710.0\lib\net40\System.Web.Mvc.dll</HintPath> + </Reference> + <Reference Include="System.Web.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <Private>True</Private> + <HintPath>..\packages\Microsoft.AspNet.Razor.2.0.20715.0\lib\net40\System.Web.Razor.dll</HintPath> + </Reference> + <Reference Include="System.Web.WebPages, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <Private>True</Private> + </Reference> + <Reference Include="System.Web.WebPages.Deployment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <Private>True</Private> + <HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Deployment.dll</HintPath> + </Reference> + <Reference Include="System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <Private>True</Private> + <HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Razor.dll</HintPath> + </Reference> + <Reference Include="Validation, Version=2.0.0.0, Culture=neutral, PublicKeyToken=2fc06f0d701809a7, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Validation.2.0.2.13022\lib\portable-windows8+net40+sl5+windowsphone8\Validation.dll</HintPath> </Reference> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" /> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> + <Import Project="$(SolutionDir)\.nuget\nuget.targets" /> </Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Core/IHostFactories.cs b/src/DotNetOpenAuth.Core/IHostFactories.cs new file mode 100644 index 0000000..d171ed8 --- /dev/null +++ b/src/DotNetOpenAuth.Core/IHostFactories.cs @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------- +// <copyright file="IHostFactories.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Text; + using System.Threading.Tasks; + + /// <summary> + /// Provides the host application or tests with the ability to create standard message handlers or stubs. + /// </summary> + public interface IHostFactories { + /// <summary> + /// Initializes a new instance of a concrete derivation of <see cref="HttpMessageHandler"/> + /// to be used for outbound HTTP traffic. + /// </summary> + /// <returns>An instance of <see cref="HttpMessageHandler"/>.</returns> + /// <remarks> + /// An instance of <see cref="WebRequestHandler"/> is recommended where available; + /// otherwise an instance of <see cref="HttpClientHandler"/> is recommended. + /// </remarks> + HttpMessageHandler CreateHttpMessageHandler(); + + /// <summary> + /// Initializes a new instance of the <see cref="HttpClient"/> class + /// to be used for outbound HTTP traffic. + /// </summary> + /// <param name="handler"> + /// The handler to pass to the <see cref="HttpClient"/> constructor. + /// May be null to use the default that would be provided by <see cref="CreateHttpMessageHandler"/>. + /// </param> + /// <returns>An instance of <see cref="HttpClient"/>.</returns> + HttpClient CreateHttpClient(HttpMessageHandler handler = null); + } +} diff --git a/src/DotNetOpenAuth.Core/IRequireHostFactories.cs b/src/DotNetOpenAuth.Core/IRequireHostFactories.cs new file mode 100644 index 0000000..60fa970 --- /dev/null +++ b/src/DotNetOpenAuth.Core/IRequireHostFactories.cs @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------- +// <copyright file="IRequireHostFactories.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + /// <summary> + /// An interface implemented by DotNetOpenAuth extensions that need to create host-specific instances of specific interfaces. + /// </summary> + public interface IRequireHostFactories { + /// <summary> + /// Gets or sets the host factories used by this instance. + /// </summary> + /// <value> + /// The host factories. + /// </value> + IHostFactories HostFactories { get; set; } + } +} diff --git a/src/DotNetOpenAuth.Core/Logger.cs b/src/DotNetOpenAuth.Core/Logger.cs index 975b391..d14fd8a 100644 --- a/src/DotNetOpenAuth.Core/Logger.cs +++ b/src/DotNetOpenAuth.Core/Logger.cs @@ -6,11 +6,11 @@ namespace DotNetOpenAuth { using System; - using System.Diagnostics.Contracts; using System.Globalization; using DotNetOpenAuth.Loggers; using DotNetOpenAuth.Messaging; using log4net.Core; + using Validation; /// <summary> /// A general logger for the entire DotNetOpenAuth library. @@ -177,7 +177,7 @@ namespace DotNetOpenAuth { /// <param name="name">The name of the log to initialize.</param> /// <returns>The <see cref="ILog"/> instance of the logger to use.</returns> private static ILog InitializeFacade(string name) { - ILog result = Log4NetLogger.Initialize(name) ?? TraceLogger.Initialize(name) ?? NoOpLogger.Initialize(); + ILog result = Log4NetLogger.Initialize(name) ?? NLogLogger.Initialize(name) ?? TraceLogger.Initialize(name) ?? NoOpLogger.Initialize(); return result; } } diff --git a/src/DotNetOpenAuth.Core/Loggers/NLogLogger.cs b/src/DotNetOpenAuth.Core/Loggers/NLogLogger.cs new file mode 100644 index 0000000..b5d883d --- /dev/null +++ b/src/DotNetOpenAuth.Core/Loggers/NLogLogger.cs @@ -0,0 +1,222 @@ +// <auto-generated /> + +namespace DotNetOpenAuth.Loggers { + using System; + using System.Globalization; + using System.IO; + using System.Reflection; + + internal class NLogLogger : ILog { + private NLog.Logger nLogLogger; + + private NLogLogger(NLog.Logger logger) { + this.nLogLogger = logger; + } + + #region ILog Members + + public bool IsDebugEnabled { + get { return this.nLogLogger.IsDebugEnabled; } + } + + public bool IsInfoEnabled { + get { return this.nLogLogger.IsInfoEnabled; } + } + + public bool IsWarnEnabled { + get { return this.nLogLogger.IsWarnEnabled; } + } + + public bool IsErrorEnabled { + get { return this.nLogLogger.IsErrorEnabled; } + } + + public bool IsFatalEnabled { + get { return this.nLogLogger.IsFatalEnabled; } + } + + #endregion + + private static bool IsNLogPresent { + get { + try { + Assembly.Load("NLog"); + return true; + } catch (FileNotFoundException) { + return false; + } + } + } + + #region ILog methods + + public void Debug(object message) { + this.nLogLogger.Debug(message); + } + + public void Debug(object message, Exception exception) { + this.nLogLogger.DebugException(String.Format("{0}", message), exception); + } + + public void DebugFormat(string format, params object[] args) { + this.nLogLogger.Debug(CultureInfo.InvariantCulture, format, args); + } + + public void DebugFormat(string format, object arg0) { + this.nLogLogger.Debug(format, arg0); + } + + public void DebugFormat(string format, object arg0, object arg1) { + this.nLogLogger.Debug(format, arg0, arg1); + } + + public void DebugFormat(string format, object arg0, object arg1, object arg2) { + this.nLogLogger.Debug(format, arg0, arg1, arg2); + } + + public void DebugFormat(IFormatProvider provider, string format, params object[] args) { + this.nLogLogger.Debug(provider, format, args); + } + + public void Info(object message) { + this.nLogLogger.Info(message); + } + + public void Info(object message, Exception exception) { + this.nLogLogger.InfoException(String.Format("{0}", message), exception); + } + + public void InfoFormat(string format, params object[] args) { + this.nLogLogger.Info(CultureInfo.InvariantCulture, format, args); + } + + public void InfoFormat(string format, object arg0) { + this.nLogLogger.Info(format, arg0); + } + + public void InfoFormat(string format, object arg0, object arg1) { + this.nLogLogger.Info(format, arg0, arg1); + } + + public void InfoFormat(string format, object arg0, object arg1, object arg2) { + this.nLogLogger.Info(format, arg0, arg1, arg2); + } + + public void InfoFormat(IFormatProvider provider, string format, params object[] args) { + this.nLogLogger.Info(provider, format, args); + } + + public void Warn(object message) { + this.nLogLogger.Warn(message); + } + + public void Warn(object message, Exception exception) { + this.nLogLogger.Warn(String.Format("{0}", message), exception); + } + + public void WarnFormat(string format, params object[] args) { + this.nLogLogger.Warn(CultureInfo.InvariantCulture, format, args); + } + + public void WarnFormat(string format, object arg0) { + this.nLogLogger.Warn(format, arg0); + } + + public void WarnFormat(string format, object arg0, object arg1) { + this.nLogLogger.Warn(format, arg0, arg1); + } + + public void WarnFormat(string format, object arg0, object arg1, object arg2) { + this.nLogLogger.Warn(format, arg0, arg1, arg2); + } + + public void WarnFormat(IFormatProvider provider, string format, params object[] args) { + this.nLogLogger.Warn(provider, format, args); + } + + public void Error(object message) { + this.nLogLogger.Error(message); + } + + public void Error(object message, Exception exception) { + this.nLogLogger.Error(String.Format("{0}", message), exception); + } + + public void ErrorFormat(string format, params object[] args) { + this.nLogLogger.Error(CultureInfo.InvariantCulture, format, args); + } + + public void ErrorFormat(string format, object arg0) { + this.nLogLogger.Error(format, arg0); + } + + public void ErrorFormat(string format, object arg0, object arg1) { + this.nLogLogger.Error(format, arg0, arg1); + } + + public void ErrorFormat(string format, object arg0, object arg1, object arg2) { + this.nLogLogger.Error(format, arg0, arg1, arg2); + } + + public void ErrorFormat(IFormatProvider provider, string format, params object[] args) { + this.nLogLogger.Error(provider, format, args); + } + + public void Fatal(object message) { + this.nLogLogger.Fatal(message); + } + + public void Fatal(object message, Exception exception) { + this.nLogLogger.Fatal(String.Format("{0}", message), exception); + } + + public void FatalFormat(string format, params object[] args) { + this.nLogLogger.Fatal(CultureInfo.InvariantCulture, format, args); + } + + public void FatalFormat(string format, object arg0) { + this.nLogLogger.Fatal(format, arg0); + } + + public void FatalFormat(string format, object arg0, object arg1) { + this.nLogLogger.Fatal(format, arg0, arg1); + } + + public void FatalFormat(string format, object arg0, object arg1, object arg2) { + this.nLogLogger.Fatal(format, arg0, arg1, arg2); + } + + public void FatalFormat(IFormatProvider provider, string format, params object[] args) { + this.nLogLogger.Fatal(provider, format, args); + } + + #endregion + + /// <summary> + /// Returns a new NLog logger if it exists, or returns null if the assembly cannot be found. + /// </summary> + /// <returns>The created <see cref="ILog"/> instance.</returns> + internal static ILog Initialize(string name) { + try { + return IsNLogPresent ? CreateLogger(name) : null; + } catch (FileLoadException) { + // wrong NLog.dll version + return null; + } catch (TargetInvocationException) { + // Thrown due to some security issues on .NET 4.5. + return null; + } catch (TypeLoadException) { + // Thrown by mono (http://stackoverflow.com/questions/10805773/error-when-pushing-dotnetopenauth-to-staging-or-production-environment) + return null; + } + } + + /// <summary> + /// Creates the NLogLogger. Call ONLY after NLog.dll is known to be present. + /// </summary> + /// <returns>The created <see cref="ILog"/> instance.</returns> + private static ILog CreateLogger(string name) { + return new NLogLogger(NLog.LogManager.GetLogger(name)); + } + } +} diff --git a/src/DotNetOpenAuth.Core/MachineKeyUtil.cs b/src/DotNetOpenAuth.Core/MachineKeyUtil.cs new file mode 100644 index 0000000..eceb38c --- /dev/null +++ b/src/DotNetOpenAuth.Core/MachineKeyUtil.cs @@ -0,0 +1,354 @@ +//----------------------------------------------------------------------- +// <copyright file="MachineKeyUtil.cs" company="Microsoft"> +// Copyright (c) Microsoft. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Net; + using System.Reflection; + using System.Security.Cryptography; + using System.Text; + using System.Web; + using System.Web.Security; + + /// <summary> + /// Provides helpers that mimic the ASP.NET 4.5 MachineKey.Protect / Unprotect APIs, + /// even when running on ASP.NET 4.0. Consumers are expected to follow the same + /// conventions used by the MachineKey.Protect / Unprotect APIs (consult MSDN docs + /// for how these are meant to be used). Additionally, since this helper class + /// dynamically switches between the two based on whether the current application is + /// .NET 4.0 or 4.5, consumers should never persist output from the Protect method + /// since the implementation will change when upgrading 4.0 -> 4.5. This should be + /// used for transient data only. + /// </summary> + internal static class MachineKeyUtil { + /// <summary> + /// MachineKey implementation depending on the target .NET framework version + /// </summary> + private static readonly IMachineKey MachineKeyImpl = GetMachineKeyImpl(); + + /// <summary> + /// ProtectUnprotect delegate. + /// </summary> + /// <param name="data">The data.</param> + /// <param name="purposes">The purposes.</param> + /// <returns>Result of either Protect or Unprotect methods.</returns> + private delegate byte[] ProtectUnprotect(byte[] data, string[] purposes); + + /// <summary> + /// Abstract the MachineKey implementation in .NET 4.0 and 4.5 + /// </summary> + private interface IMachineKey { + /// <summary> + /// Protects the specified user data. + /// </summary> + /// <param name="userData">The user data.</param> + /// <param name="purposes">The purposes.</param> + /// <returns>The protected data.</returns> + byte[] Protect(byte[] userData, string[] purposes); + + /// <summary> + /// Unprotects the specified protected data. + /// </summary> + /// <param name="protectedData">The protected data.</param> + /// <param name="purposes">The purposes.</param> + /// <returns>The unprotected data.</returns> + byte[] Unprotect(byte[] protectedData, string[] purposes); + } + + /// <summary> + /// Protects the specified user data. + /// </summary> + /// <param name="userData">The user data.</param> + /// <param name="purposes">The purposes.</param> + /// <returns>The encrypted data</returns> + public static byte[] Protect(byte[] userData, params string[] purposes) { + return MachineKeyImpl.Protect(userData, purposes); + } + + /// <summary> + /// Unprotects the specified protected data. + /// </summary> + /// <param name="protectedData">The protected data.</param> + /// <param name="purposes">The purposes.</param> + /// <returns>The unencrypted data</returns> + public static byte[] Unprotect(byte[] protectedData, params string[] purposes) { + return MachineKeyImpl.Unprotect(protectedData, purposes); + } + + /// <summary> + /// Gets the machine key implementation based on the runtime framework version. + /// </summary> + /// <returns>The machine key implementation</returns> + private static IMachineKey GetMachineKeyImpl() { + // Late bind to the MachineKey.Protect / Unprotect methods only if <httpRuntime targetFramework="4.5" />. + // This helps ensure that round-tripping the payloads continues to work even if the application is + // deployed to a mixed 4.0 / 4.5 farm environment. + PropertyInfo targetFrameworkProperty = typeof(HttpRuntime).GetProperty("TargetFramework", typeof(Version)); + Version targetFramework = (targetFrameworkProperty != null) ? targetFrameworkProperty.GetValue(null, null) as Version : null; + if (targetFramework != null && targetFramework >= new Version(4, 5)) { + ProtectUnprotect protectThunk = (ProtectUnprotect)Delegate.CreateDelegate(typeof(ProtectUnprotect), typeof(MachineKey), "Protect", ignoreCase: false, throwOnBindFailure: false); + ProtectUnprotect unprotectThunk = (ProtectUnprotect)Delegate.CreateDelegate(typeof(ProtectUnprotect), typeof(MachineKey), "Unprotect", ignoreCase: false, throwOnBindFailure: false); + if (protectThunk != null && unprotectThunk != null) { + return new MachineKey45(protectThunk, unprotectThunk); // ASP.NET 4.5 + } + } + + return new MachineKey40(); // ASP.NET 4.0 + } + + /// <summary> + /// On ASP.NET 4.0, we perform some transforms which mimic the behaviors of MachineKey.Protect + /// and Unprotect. + /// </summary> + private sealed class MachineKey40 : IMachineKey { + /// <summary> + /// This is the magic header that identifies a MachineKey40 payload. + /// It helps differentiate this from other encrypted payloads.</summary> + private const uint MagicHeader = 0x8519140c; + + /// <summary> + /// The SHA-256 factory to be used. + /// </summary> + private static readonly Func<SHA256> sha256Factory = GetSHA256Factory(); + + /// <summary> + /// Protects the specified user data. + /// </summary> + /// <param name="userData">The user data.</param> + /// <param name="purposes">The purposes.</param> + /// <returns>The protected data</returns> + public byte[] Protect(byte[] userData, string[] purposes) { + if (userData == null) { + throw new ArgumentNullException("userData"); + } + + // dataWithHeader = {magic header} .. {purposes} .. {userData} + byte[] dataWithHeader = new byte[checked(4 /* magic header */ + (256 / 8) /* purposes */ + userData.Length)]; + unchecked { + dataWithHeader[0] = (byte)(MagicHeader >> 24); + dataWithHeader[1] = (byte)(MagicHeader >> 16); + dataWithHeader[2] = (byte)(MagicHeader >> 8); + dataWithHeader[3] = (byte)MagicHeader; + } + byte[] purposeHash = ComputeSHA256(purposes); + Buffer.BlockCopy(purposeHash, 0, dataWithHeader, 4, purposeHash.Length); + Buffer.BlockCopy(userData, 0, dataWithHeader, 4 + (256 / 8), userData.Length); + + // encrypt + sign + string hexValue = MachineKey.Encode(dataWithHeader, MachineKeyProtection.All); + + // convert hex -> binary + byte[] binary = HexToBinary(hexValue); + return binary; + } + + /// <summary> + /// Unprotects the specified protected data. + /// </summary> + /// <param name="protectedData">The protected data.</param> + /// <param name="purposes">The purposes.</param> + /// <returns>The unprotected data</returns> + public byte[] Unprotect(byte[] protectedData, string[] purposes) { + if (protectedData == null) { + throw new ArgumentNullException("protectedData"); + } + + // convert binary -> hex and calculate what the purpose should read + string hexEncodedData = BinaryToHex(protectedData); + byte[] purposeHash = ComputeSHA256(purposes); + + try { + // decrypt / verify signature + byte[] dataWithHeader = MachineKey.Decode(hexEncodedData, MachineKeyProtection.All); + + // validate magic header and purpose string + if (dataWithHeader != null + && dataWithHeader.Length >= (4 + (256 / 8)) + && (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(dataWithHeader, 0)) == MagicHeader + && AreByteArraysEqual(new ArraySegment<byte>(purposeHash), new ArraySegment<byte>(dataWithHeader, 4, 256 / 8))) { + // validation succeeded + byte[] userData = new byte[dataWithHeader.Length - 4 - (256 / 8)]; + Buffer.BlockCopy(dataWithHeader, 4 + (256 / 8), userData, 0, userData.Length); + return userData; + } + } + catch { + // swallow since will be rethrown immediately below + } + + // if we reached this point, some cryptographic operation failed + throw new CryptographicException(Strings.Generic_CryptoFailure); + } + + /// <summary> + /// Convert bytes to hex string. + /// </summary> + /// <param name="binary">The input array.</param> + /// <returns>Hex string</returns> + internal static string BinaryToHex(byte[] binary) { + StringBuilder builder = new StringBuilder(checked(binary.Length * 2)); + for (int i = 0; i < binary.Length; i++) { + byte b = binary[i]; + builder.Append(HexDigit(b >> 4)); + builder.Append(HexDigit(b & 0x0F)); + } + string result = builder.ToString(); + return result; + } + + /// <summary> + /// This method is specially written to take the same amount of time + /// regardless of where 'a' and 'b' differ. Please do not optimize it.</summary> + /// <param name="a">first array.</param> + /// <param name="b">second array.</param> + /// <returns><c href="true" /> if equal, others <c href="false" /></returns> + private static bool AreByteArraysEqual(ArraySegment<byte> a, ArraySegment<byte> b) { + if (a.Count != b.Count) { + return false; + } + + bool areEqual = true; + for (int i = 0; i < a.Count; i++) { + areEqual &= a.Array[a.Offset + i] == b.Array[b.Offset + i]; + } + return areEqual; + } + + /// <summary> + /// Computes a SHA256 hash over all of the input parameters. + /// Each parameter is UTF8 encoded and preceded by a 7-bit encoded</summary> + /// integer describing the encoded byte length of the string. + /// <param name="parameters">The parameters.</param> + /// <returns>The output hash</returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is resilient to double-Dispose")] + private static byte[] ComputeSHA256(IList<string> parameters) { + using (MemoryStream ms = new MemoryStream()) { + using (BinaryWriter bw = new BinaryWriter(ms)) { + if (parameters != null) { + foreach (string parameter in parameters) { + bw.Write(parameter); // also writes the length as a prefix; unambiguous + } + bw.Flush(); + } + + using (SHA256 sha256 = sha256Factory()) { + byte[] retVal = sha256.ComputeHash(ms.GetBuffer(), 0, checked((int)ms.Length)); + return retVal; + } + } + } + } + + /// <summary> + /// Gets the SHA-256 factory. + /// </summary> + /// <returns>SHA256 factory</returns> + private static Func<SHA256> GetSHA256Factory() { + // Note: ASP.NET 4.5 always prefers CNG, but the CNG algorithms are not that + // performant on 4.0 and below. The following list is optimized for speed + // given our scenarios. + if (!CryptoConfig.AllowOnlyFipsAlgorithms) { + // This provider is not FIPS-compliant, so we can't use it if FIPS compliance + // is mandatory. + return () => new SHA256Managed(); + } + + try { + using (SHA256Cng sha256 = new SHA256Cng()) { + return () => new SHA256Cng(); + } + } + catch (PlatformNotSupportedException) { + // CNG not supported (perhaps because we're not on Windows Vista or above); move on + } + + // If all else fails, fall back to CAPI. + return () => new SHA256CryptoServiceProvider(); + } + + /// <summary> + /// Convert to hex character + /// </summary> + /// <param name="value">The value to be converted.</param> + /// <returns>Hex character</returns> + private static char HexDigit(int value) { + return (char)(value > 9 ? value + '7' : value + '0'); + } + + /// <summary> + /// Convert hdex string to bytes. + /// </summary> + /// <param name="hex">Input hex string.</param> + /// <returns>The bytes</returns> + private static byte[] HexToBinary(string hex) { + int size = hex.Length / 2; + byte[] bytes = new byte[size]; + for (int idx = 0; idx < size; idx++) { + bytes[idx] = (byte)((HexValue(hex[idx * 2]) << 4) + HexValue(hex[(idx * 2) + 1])); + } + return bytes; + } + + /// <summary> + /// Convert hex digit to byte. + /// </summary> + /// <param name="digit">The hex digit.</param> + /// <returns>The byte</returns> + private static int HexValue(char digit) { + return digit > '9' ? digit - '7' : digit - '0'; + } + } + + /// <summary> + /// On ASP.NET 4.5, we can just delegate to MachineKey.Protect and MachineKey.Unprotect directly, + /// which contain optimized code paths. + /// </summary> + private sealed class MachineKey45 : IMachineKey { + /// <summary> + /// Protect thunk + /// </summary> + private readonly ProtectUnprotect protectThunk; + + /// <summary> + /// Unprotect thunk + /// </summary> + private readonly ProtectUnprotect unprotectThunk; + + /// <summary> + /// Initializes a new instance of the <see cref="MachineKey45"/> class. + /// </summary> + /// <param name="protectThunk">The protect thunk.</param> + /// <param name="unprotectThunk">The unprotect thunk.</param> + public MachineKey45(ProtectUnprotect protectThunk, ProtectUnprotect unprotectThunk) { + this.protectThunk = protectThunk; + this.unprotectThunk = unprotectThunk; + } + + /// <summary> + /// Protects the specified user data. + /// </summary> + /// <param name="userData">The user data.</param> + /// <param name="purposes">The purposes.</param> + /// <returns>The protected data</returns> + public byte[] Protect(byte[] userData, string[] purposes) { + return this.protectThunk(userData, purposes); + } + + /// <summary> + /// Unprotects the specified protected data. + /// </summary> + /// <param name="protectedData">The protected data.</param> + /// <param name="purposes">The purposes.</param> + /// <returns>The unprotected data</returns> + public byte[] Unprotect(byte[] protectedData, string[] purposes) { + return this.unprotectThunk(protectedData, purposes); + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/BinaryDataBagFormatter.cs b/src/DotNetOpenAuth.Core/Messaging/BinaryDataBagFormatter.cs index 4f4bf0e..554205a 100644 --- a/src/DotNetOpenAuth.Core/Messaging/BinaryDataBagFormatter.cs +++ b/src/DotNetOpenAuth.Core/Messaging/BinaryDataBagFormatter.cs @@ -8,12 +8,12 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using DotNetOpenAuth.Messaging.Bindings; + using Validation; /// <summary> /// A compact binary <see cref="DataBag"/> serialization class. @@ -45,7 +45,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> protected internal BinaryDataBagFormatter(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) : base(cryptoKeyStore, bucket, signed, encrypted, compressed, minimumAge, maximumAge, decodeOnceOnly) { - Requires.True((cryptoKeyStore != null && bucket != null) || (!signed && !encrypted), null); + Requires.That((cryptoKeyStore != null && bucket != null) || (!signed && !encrypted), null, "Signing or encryption requires a crypto key store and bucket."); } /// <summary> diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs index 4cb5337..0439908 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs @@ -8,11 +8,11 @@ namespace DotNetOpenAuth.Messaging.Bindings { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.Linq; using System.Security.Cryptography; using System.Text; using DotNetOpenAuth.Messaging; + using Validation; /// <summary> /// Provides RSA encryption of symmetric keys to protect them from a theft of @@ -42,7 +42,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { public AsymmetricCryptoKeyStoreWrapper(ICryptoKeyStore dataStore, RSACryptoServiceProvider asymmetricCrypto) { Requires.NotNull(dataStore, "dataStore"); Requires.NotNull(asymmetricCrypto, "asymmetricCrypto"); - Requires.True(!asymmetricCrypto.PublicOnly, "asymmetricCrypto"); + Requires.That(!asymmetricCrypto.PublicOnly, "asymmetricCrypto", "Private key required."); this.dataStore = dataStore; this.asymmetricCrypto = asymmetricCrypto; } @@ -138,9 +138,9 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// <param name="decrypted">The decrypted key.</param> internal CachedCryptoKey(CryptoKey encrypted, CryptoKey decrypted) : base(decrypted.Key, decrypted.ExpiresUtc) { - Contract.Requires(encrypted != null); - Contract.Requires(decrypted != null); - Contract.Requires(encrypted.ExpiresUtc == decrypted.ExpiresUtc); + Requires.NotNull(encrypted, "encrypted"); + Requires.NotNull(decrypted, "decrypted"); + Requires.That(encrypted.ExpiresUtc == decrypted.ExpiresUtc, "encrypted", "encrypted and decrypted expirations must equal."); this.EncryptedKey = encrypted.Key; } @@ -149,16 +149,6 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// Gets the encrypted key. /// </summary> internal byte[] EncryptedKey { get; private set; } - - /// <summary> - /// Invariant conditions. - /// </summary> - [ContractInvariantMethod] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code contracts")] - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")] - private void ObjectInvariant() { - Contract.Invariant(this.EncryptedKey != null); - } } } } diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKey.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKey.cs index 3fa50d4..d6fef62 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKey.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKey.cs @@ -8,10 +8,10 @@ namespace DotNetOpenAuth.Messaging.Bindings { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; + using Validation; /// <summary> /// A cryptographic key and metadata concerning it. @@ -34,7 +34,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// <param name="expiresUtc">The expires UTC.</param> public CryptoKey(byte[] key, DateTime expiresUtc) { Requires.NotNull(key, "key"); - Requires.True(expiresUtc.Kind == DateTimeKind.Utc, "expiresUtc"); + Requires.That(expiresUtc.Kind == DateTimeKind.Utc, "expiresUtc", "Time must be expressed in UTC."); this.key = key; this.expiresUtc = expiresUtc; } @@ -45,7 +45,6 @@ namespace DotNetOpenAuth.Messaging.Bindings { [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's a buffer")] public byte[] Key { get { - Contract.Ensures(Contract.Result<byte[]>() != null); return this.key; } } @@ -55,7 +54,6 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// </summary> public DateTime ExpiresUtc { get { - Contract.Ensures(Contract.Result<DateTime>().Kind == DateTimeKind.Utc); return this.expiresUtc; } } diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKeyCollisionException.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKeyCollisionException.cs index 1fe4493..7507b31 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKeyCollisionException.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/CryptoKeyCollisionException.cs @@ -56,11 +56,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*"/> /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter"/> /// </PermissionSet> -#if CLR4 [System.Security.SecurityCritical] -#else - [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] -#endif public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { base.GetObjectData(info, context); throw new NotImplementedException(); diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs index 88b8fed..8c5db3c 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs @@ -6,8 +6,8 @@ namespace DotNetOpenAuth.Messaging.Bindings { using System; - using System.Diagnostics.Contracts; using System.Globalization; + using Validation; /// <summary> /// An exception thrown when a message is received that exceeds the maximum message age limit. @@ -21,7 +21,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// <param name="faultedMessage">The expired message.</param> public ExpiredMessageException(DateTime utcExpirationDate, IProtocolMessage faultedMessage) : base(string.Format(CultureInfo.CurrentCulture, MessagingStrings.ExpiredMessage, utcExpirationDate.ToLocalTime(), DateTime.Now), faultedMessage) { - Requires.True(utcExpirationDate.Kind == DateTimeKind.Utc, "utcExpirationDate"); + Requires.Argument(utcExpirationDate.Kind == DateTimeKind.Utc, "utcExpirationDate", "Time must be expressed as UTC."); } /// <summary> diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/HardCodedKeyCryptoKeyStore.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/HardCodedKeyCryptoKeyStore.cs new file mode 100644 index 0000000..9bea16f --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/HardCodedKeyCryptoKeyStore.cs @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------- +// <copyright file="HardCodedKeyCryptoKeyStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Collections.Generic; + using Validation; + + /// <summary> + /// A trivial implementation of <see cref="ICryptoKeyStore"/> that has only one fixed key. + /// This is meant for simple, low-security applications. Greater security requires an + /// implementation of <see cref="ICryptoKeyStore"/> that actually stores and retrieves + /// keys from a persistent store. + /// </summary> + public class HardCodedKeyCryptoKeyStore : ICryptoKeyStore { + /// <summary> + /// The handle to report for the hard-coded key. + /// </summary> + private const string HardCodedKeyHandle = "fxd"; + + /// <summary> + /// The one crypto key singleton instance. + /// </summary> + private readonly CryptoKey OneCryptoKey; + + /// <summary> + /// Initializes a new instance of the <see cref="HardCodedKeyCryptoKeyStore"/> class. + /// </summary> + /// <param name="secretAsBase64">The 256-bit secret as a base64 encoded string.</param> + public HardCodedKeyCryptoKeyStore(string secretAsBase64) + : this(Convert.FromBase64String(Requires.NotNull(secretAsBase64, "secretAsBase64"))) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="HardCodedKeyCryptoKeyStore"/> class. + /// </summary> + /// <param name="secret">The 256-bit secret.</param> + public HardCodedKeyCryptoKeyStore(byte[] secret) { + Requires.NotNull(secret, "secret"); + this.OneCryptoKey = new CryptoKey(secret, DateTime.MaxValue.AddDays(-2).ToUniversalTime()); + } + + #region ICryptoKeyStore Members + + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns> + /// The cryptographic key, or <c>null</c> if no matching key was found. + /// </returns> + public CryptoKey GetKey(string bucket, string handle) { + if (handle == HardCodedKeyHandle) { + return this.OneCryptoKey; + } + + return null; + } + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns> + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc" />. + /// </returns> + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + return new[] { new KeyValuePair<string, CryptoKey>(HardCodedKeyHandle, this.OneCryptoKey) }; + } + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="System.NotSupportedException">Always thrown.</exception> + public void StoreKey(string bucket, string handle, CryptoKey key) { + throw new NotSupportedException(); + } + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <exception cref="System.NotSupportedException">Always thrown.</exception> + public void RemoveKey(string bucket, string handle) { + throw new NotSupportedException(); + } + + #endregion + } +}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/ICryptoKeyAndNonceStore.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/ICryptoKeyAndNonceStore.cs new file mode 100644 index 0000000..aa03504 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/ICryptoKeyAndNonceStore.cs @@ -0,0 +1,14 @@ +//----------------------------------------------------------------------- +// <copyright file="ICryptoKeyAndNonceStore.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + /// <summary> + /// A hybrid of the store interfaces that an OpenID Provider must implement, and + /// an OpenID Relying Party may implement to operate in stateful (smart) mode. + /// </summary> + public interface ICryptoKeyAndNonceStore : ICryptoKeyStore, INonceStore { + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/ICryptoKeyStore.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/ICryptoKeyStore.cs index 2e43bba..ce7bf42 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Bindings/ICryptoKeyStore.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/ICryptoKeyStore.cs @@ -8,10 +8,10 @@ namespace DotNetOpenAuth.Messaging.Bindings { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; + using Validation; /// <summary> /// A persistent store for rotating symmetric cryptographic keys. @@ -23,7 +23,6 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// of the confidentiality of the keys. One possible mitigation is to asymmetrically encrypt /// each key using a certificate installed in the server's certificate store. /// </remarks> - [ContractClass(typeof(ICryptoKeyStoreContract))] public interface ICryptoKeyStore { /// <summary> /// Gets the key in a given bucket and handle. @@ -57,62 +56,4 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// <param name="handle">The key handle. Case sensitive.</param> void RemoveKey(string bucket, string handle); } - - /// <summary> - /// Code contract for the <see cref="ICryptoKeyStore"/> interface. - /// </summary> - [ContractClassFor(typeof(ICryptoKeyStore))] - internal abstract class ICryptoKeyStoreContract : ICryptoKeyStore { - /// <summary> - /// Gets the key in a given bucket and handle. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - /// <returns> - /// The cryptographic key, or <c>null</c> if no matching key was found. - /// </returns> - CryptoKey ICryptoKeyStore.GetKey(string bucket, string handle) { - Requires.NotNullOrEmpty(bucket, "bucket"); - Requires.NotNullOrEmpty(handle, "handle"); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets a sequence of existing keys within a given bucket. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <returns> - /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. - /// </returns> - IEnumerable<KeyValuePair<string, CryptoKey>> ICryptoKeyStore.GetKeys(string bucket) { - Requires.NotNullOrEmpty(bucket, "bucket"); - Contract.Ensures(Contract.Result<IEnumerable<KeyValuePair<string, CryptoKey>>>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Stores a cryptographic key. - /// </summary> - /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> - /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> - /// <param name="key">The key to store.</param> - /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> - void ICryptoKeyStore.StoreKey(string bucket, string handle, CryptoKey key) { - Requires.NotNullOrEmpty(bucket, "bucket"); - Requires.NotNullOrEmpty(handle, "handle"); - Requires.NotNull(key, "key"); - throw new NotImplementedException(); - } - - /// <summary> - /// Removes the key. - /// </summary> - /// <param name="bucket">The bucket name. Case sensitive.</param> - /// <param name="handle">The key handle. Case sensitive.</param> - void ICryptoKeyStore.RemoveKey(string bucket, string handle) { - Requires.NotNullOrEmpty(bucket, "bucket"); - Requires.NotNullOrEmpty(handle, "handle"); - throw new NotImplementedException(); - } - } } diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/MemoryCryptoKeyAndNonceStore.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/MemoryCryptoKeyAndNonceStore.cs new file mode 100644 index 0000000..1484ec7 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/MemoryCryptoKeyAndNonceStore.cs @@ -0,0 +1,126 @@ +//----------------------------------------------------------------------- +// <copyright file="MemoryCryptoKeyAndNonceStore.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Collections.Generic; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// An in-memory store for Providers, suitable for single server, single process + /// ASP.NET web sites. + /// </summary> + /// <remarks> + /// This class provides only a basic implementation that is likely to work + /// out of the box on most single-server web sites. It is highly recommended + /// that high traffic web sites consider using a database to store the information + /// used by an OpenID Provider and write a custom implementation of the + /// <see cref="ICryptoKeyAndNonceStore"/> interface to use instead of this + /// class. + /// </remarks> + public class MemoryCryptoKeyAndNonceStore : ICryptoKeyAndNonceStore { + /// <summary> + /// The nonce store to use. + /// </summary> + private readonly INonceStore nonceStore; + + /// <summary> + /// The crypto key store where symmetric keys are persisted. + /// </summary> + private readonly ICryptoKeyStore cryptoKeyStore; + + /// <summary> + /// Initializes a new instance of the <see cref="MemoryCryptoKeyAndNonceStore" /> class + /// with a default max nonce lifetime of 5 minutes. + /// </summary> + public MemoryCryptoKeyAndNonceStore() + : this(TimeSpan.FromMinutes(5)) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="MemoryCryptoKeyAndNonceStore"/> class. + /// </summary> + /// <param name="maximumMessageAge">The maximum time to live of a message that might carry a nonce.</param> + public MemoryCryptoKeyAndNonceStore(TimeSpan maximumMessageAge) { + this.nonceStore = new MemoryNonceStore(maximumMessageAge); + this.cryptoKeyStore = new MemoryCryptoKeyStore(); + } + + #region INonceStore Members + + /// <summary> + /// Stores a given nonce and timestamp. + /// </summary> + /// <param name="context">The context, or namespace, within which the <paramref name="nonce"/> must be unique.</param> + /// <param name="nonce">A series of random characters.</param> + /// <param name="timestampUtc">The timestamp that together with the nonce string make it unique. + /// The timestamp may also be used by the data store to clear out old nonces.</param> + /// <returns> + /// True if the nonce+timestamp (combination) was not previously in the database. + /// False if the nonce was stored previously with the same timestamp. + /// </returns> + /// <remarks> + /// The nonce must be stored for no less than the maximum time window a message may + /// be processed within before being discarded as an expired message. + /// If the binding element is applicable to your channel, this expiration window + /// is retrieved or set using the + /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property. + /// </remarks> + public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { + return this.nonceStore.StoreNonce(context, nonce, timestampUtc); + } + + #endregion + + #region ICryptoKeyStore + + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns> + /// The cryptographic key, or <c>null</c> if no matching key was found. + /// </returns> + public CryptoKey GetKey(string bucket, string handle) { + return this.cryptoKeyStore.GetKey(bucket, handle); + } + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns> + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. + /// </returns> + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + return this.cryptoKeyStore.GetKeys(bucket); + } + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> + public void StoreKey(string bucket, string handle, CryptoKey key) { + this.cryptoKeyStore.StoreKey(bucket, handle, key); + } + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + public void RemoveKey(string bucket, string handle) { + this.cryptoKeyStore.RemoveKey(bucket, handle); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/NonceMemoryStore.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/MemoryNonceStore.cs index d069d66..f1d1d3e 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Bindings/NonceMemoryStore.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/MemoryNonceStore.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------- -// <copyright file="NonceMemoryStore.cs" company="Outercurve Foundation"> +// <copyright file="MemoryNonceStore.cs" company="Outercurve Foundation"> // Copyright (c) Outercurve Foundation. All rights reserved. // </copyright> //----------------------------------------------------------------------- @@ -8,14 +8,12 @@ namespace DotNetOpenAuth.Messaging.Bindings { using System; using System.Collections.Generic; using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging.Bindings; /// <summary> /// An in-memory nonce store. Useful for single-server web applications. /// NOT for web farms. /// </summary> - internal class NonceMemoryStore : INonceStore { + internal class MemoryNonceStore : INonceStore { /// <summary> /// How frequently we should take time to clear out old nonces. /// </summary> @@ -45,17 +43,17 @@ namespace DotNetOpenAuth.Messaging.Bindings { private int nonceClearingCounter; /// <summary> - /// Initializes a new instance of the <see cref="NonceMemoryStore"/> class. + /// Initializes a new instance of the <see cref="MemoryNonceStore"/> class. /// </summary> - internal NonceMemoryStore() + internal MemoryNonceStore() : this(StandardExpirationBindingElement.MaximumMessageAge) { } /// <summary> - /// Initializes a new instance of the <see cref="NonceMemoryStore"/> class. + /// Initializes a new instance of the <see cref="MemoryNonceStore"/> class. /// </summary> /// <param name="maximumMessageAge">The maximum age a message can be before it is discarded.</param> - internal NonceMemoryStore(TimeSpan maximumMessageAge) { + internal MemoryNonceStore(TimeSpan maximumMessageAge) { this.maximumMessageAge = maximumMessageAge; } diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardExpirationBindingElement.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardExpirationBindingElement.cs index 7ab78db..f19d4bd 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardExpirationBindingElement.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardExpirationBindingElement.cs @@ -6,6 +6,8 @@ namespace DotNetOpenAuth.Messaging.Bindings { using System; + using System.Threading; + using System.Threading.Tasks; using DotNetOpenAuth.Configuration; /// <summary> @@ -14,6 +16,16 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// </summary> internal class StandardExpirationBindingElement : IChannelBindingElement { /// <summary> + /// A reusable pre-completed task that may be returned multiple times to reduce GC pressure. + /// </summary> + private static readonly Task<MessageProtections?> NullTask = Task.FromResult<MessageProtections?>(null); + + /// <summary> + /// A reusable pre-completed task that may be returned multiple times to reduce GC pressure. + /// </summary> + private static readonly Task<MessageProtections?> CompletedExpirationTask = Task.FromResult<MessageProtections?>(MessageProtections.Expiration); + + /// <summary> /// Initializes a new instance of the <see cref="StandardExpirationBindingElement"/> class. /// </summary> internal StandardExpirationBindingElement() { @@ -51,24 +63,30 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// Sets the timestamp on an outgoing message. /// </summary> /// <param name="message">The outgoing message.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. /// </returns> - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection" /> properties where applicable. + /// </remarks> + public Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { IExpiringProtocolMessage expiringMessage = message as IExpiringProtocolMessage; if (expiringMessage != null) { expiringMessage.UtcCreationDate = DateTime.UtcNow; - return MessageProtections.Expiration; + return CompletedExpirationTask; } - return null; + return NullTask; } /// <summary> /// Reads the timestamp on a message and throws an exception if the message is too old. /// </summary> /// <param name="message">The incoming message.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. @@ -78,7 +96,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// Thrown when the binding element rules indicate that this message is invalid and should /// NOT be processed. /// </exception> - public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + public Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { IExpiringProtocolMessage expiringMessage = message as IExpiringProtocolMessage; if (expiringMessage != null) { // Yes the UtcCreationDate is supposed to always be in UTC already, @@ -96,10 +114,10 @@ namespace DotNetOpenAuth.Messaging.Bindings { MessagingStrings.MessageTimestampInFuture, creationDate); - return MessageProtections.Expiration; + return CompletedExpirationTask; } - return null; + return NullTask; } #endregion diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardReplayProtectionBindingElement.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardReplayProtectionBindingElement.cs index 7e39536..65c7882 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardReplayProtectionBindingElement.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/StandardReplayProtectionBindingElement.cs @@ -7,13 +7,25 @@ namespace DotNetOpenAuth.Messaging.Bindings { using System; using System.Diagnostics; - using System.Diagnostics.Contracts; + using System.Threading; + using System.Threading.Tasks; + using Validation; /// <summary> /// A binding element that checks/verifies a nonce message part. /// </summary> internal class StandardReplayProtectionBindingElement : IChannelBindingElement { /// <summary> + /// A reusable, precompleted task that can be returned many times to reduce GC pressure. + /// </summary> + private static readonly Task<MessageProtections?> NullTask = Task.FromResult<MessageProtections?>(null); + + /// <summary> + /// A reusable, precompleted task that can be returned many times to reduce GC pressure. + /// </summary> + private static readonly Task<MessageProtections?> CompletedReplayProtectionTask = Task.FromResult<MessageProtections?>(MessageProtections.ReplayProtection); + + /// <summary> /// These are the characters that may be chosen from when forming a random nonce. /// </summary> private const string AllowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -96,30 +108,36 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// Applies a nonce to the message. /// </summary> /// <param name="message">The message to apply replay protection to.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. /// </returns> - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + public Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage; if (nonceMessage != null) { nonceMessage.Nonce = this.GenerateUniqueFragment(); - return MessageProtections.ReplayProtection; + return CompletedReplayProtectionTask; } - return null; + return NullTask; } /// <summary> /// Verifies that the nonce in an incoming message has not been seen before. /// </summary> /// <param name="message">The incoming message.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. /// </returns> /// <exception cref="ReplayedMessageException">Thrown when the nonce check revealed a replayed message.</exception> - public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection" /> properties where applicable. + /// </remarks> + public Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage; if (nonceMessage != null && nonceMessage.Nonce != null) { ErrorUtilities.VerifyProtocol(nonceMessage.Nonce.Length > 0 || this.AllowZeroLengthNonce, MessagingStrings.InvalidNonceReceived); @@ -129,10 +147,10 @@ namespace DotNetOpenAuth.Messaging.Bindings { throw new ReplayedMessageException(message); } - return MessageProtections.ReplayProtection; + return CompletedReplayProtectionTask; } - return null; + return NullTask; } #endregion diff --git a/src/DotNetOpenAuth.Core/Messaging/CachedDirectWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/CachedDirectWebResponse.cs deleted file mode 100644 index 16e92a8..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/CachedDirectWebResponse.cs +++ /dev/null @@ -1,184 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="CachedDirectWebResponse.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using System.Text; - - /// <summary> - /// Cached details on the response from a direct web request to a remote party. - /// </summary> - [ContractVerification(true)] - [DebuggerDisplay("{Status} {ContentType.MediaType}, length: {ResponseStream.Length}")] - internal class CachedDirectWebResponse : IncomingWebResponse { - /// <summary> - /// A seekable, repeatable response stream. - /// </summary> - private MemoryStream responseStream; - - /// <summary> - /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class. - /// </summary> - internal CachedDirectWebResponse() { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class. - /// </summary> - /// <param name="requestUri">The request URI.</param> - /// <param name="response">The response.</param> - /// <param name="maximumBytesToRead">The maximum bytes to read.</param> - internal CachedDirectWebResponse(Uri requestUri, HttpWebResponse response, int maximumBytesToRead) - : base(requestUri, response) { - Requires.NotNull(requestUri, "requestUri"); - Requires.NotNull(response, "response"); - this.responseStream = CacheNetworkStreamAndClose(response, maximumBytesToRead); - - // BUGBUG: if the response was exactly maximumBytesToRead, we'll incorrectly believe it was truncated. - this.ResponseTruncated = this.responseStream.Length == maximumBytesToRead; - } - - /// <summary> - /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class. - /// </summary> - /// <param name="requestUri">The request URI.</param> - /// <param name="responseUri">The final URI to respond to the request.</param> - /// <param name="headers">The headers.</param> - /// <param name="statusCode">The status code.</param> - /// <param name="contentType">Type of the content.</param> - /// <param name="contentEncoding">The content encoding.</param> - /// <param name="responseStream">The response stream.</param> - internal CachedDirectWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding, MemoryStream responseStream) - : base(requestUri, responseUri, headers, statusCode, contentType, contentEncoding) { - Requires.NotNull(requestUri, "requestUri"); - Requires.NotNull(responseStream, "responseStream"); - this.responseStream = responseStream; - } - - /// <summary> - /// Gets a value indicating whether the cached response stream was - /// truncated to a maximum allowable length. - /// </summary> - public bool ResponseTruncated { get; private set; } - - /// <summary> - /// Gets the body of the HTTP response. - /// </summary> - public override Stream ResponseStream { - get { return this.responseStream; } - } - - /// <summary> - /// Gets or sets the cached response stream. - /// </summary> - internal MemoryStream CachedResponseStream { - get { return this.responseStream; } - set { this.responseStream = value; } - } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns>The text reader, initialized for the proper encoding.</returns> - public override StreamReader GetResponseReader() { - this.ResponseStream.Seek(0, SeekOrigin.Begin); - string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding]; - Encoding encoding = null; - if (!string.IsNullOrEmpty(contentEncoding)) { - try { - encoding = Encoding.GetEncoding(contentEncoding); - } catch (ArgumentException ex) { - Logger.Messaging.ErrorFormat("Encoding.GetEncoding(\"{0}\") threw ArgumentException: {1}", contentEncoding, ex); - } - } - - return encoding != null ? new StreamReader(this.ResponseStream, encoding) : new StreamReader(this.ResponseStream); - } - - /// <summary> - /// Gets the body of the response as a string. - /// </summary> - /// <returns>The entire body of the response.</returns> - internal string GetResponseString() { - if (this.ResponseStream != null) { - string value = this.GetResponseReader().ReadToEnd(); - this.ResponseStream.Seek(0, SeekOrigin.Begin); - return value; - } else { - return null; - } - } - - /// <summary> - /// Gets an offline snapshot version of this instance. - /// </summary> - /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> - /// <returns>A snapshot version of this instance.</returns> - /// <remarks> - /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot - /// will automatically close and dispose of the underlying response stream. - /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will - /// be the self same instance. - /// </remarks> - internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { - return this; - } - - /// <summary> - /// Sets the response to some string, encoded as UTF-8. - /// </summary> - /// <param name="body">The string to set the response to.</param> - internal void SetResponse(string body) { - if (body == null) { - this.responseStream = null; - return; - } - - Encoding encoding = Encoding.UTF8; - this.Headers[HttpResponseHeader.ContentEncoding] = encoding.HeaderName; - this.responseStream = new MemoryStream(); - StreamWriter writer = new StreamWriter(this.ResponseStream, encoding); - writer.Write(body); - writer.Flush(); - this.ResponseStream.Seek(0, SeekOrigin.Begin); - } - - /// <summary> - /// Caches the network stream and closes it if it is open. - /// </summary> - /// <param name="response">The response whose stream is to be cloned.</param> - /// <param name="maximumBytesToRead">The maximum bytes to cache.</param> - /// <returns>The seekable Stream instance that contains a copy of what was returned in the HTTP response.</returns> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "No localization required.")] - private static MemoryStream CacheNetworkStreamAndClose(HttpWebResponse response, int maximumBytesToRead) { - Requires.NotNull(response, "response"); - Contract.Ensures(Contract.Result<MemoryStream>() != null); - - // Now read and cache the network stream - Stream networkStream = response.GetResponseStream(); - MemoryStream cachedStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : Math.Min((int)response.ContentLength, maximumBytesToRead)); - try { - Contract.Assume(networkStream.CanRead, "HttpWebResponse.GetResponseStream() always returns a readable stream."); // CC missing - Contract.Assume(cachedStream.CanWrite, "This is a MemoryStream -- it's always writable."); // CC missing - networkStream.CopyTo(cachedStream); - cachedStream.Seek(0, SeekOrigin.Begin); - - networkStream.Dispose(); - response.Close(); - - return cachedStream; - } catch { - cachedStream.Dispose(); - throw; - } - } - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/Channel.cs b/src/DotNetOpenAuth.Core/Messaging/Channel.cs index f8ac6a1..cc61b25 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Channel.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Channel.cs @@ -17,20 +17,23 @@ namespace DotNetOpenAuth.Messaging { using System.Linq; using System.Net; using System.Net.Cache; + using System.Net.Http; + using System.Net.Http.Headers; using System.Net.Mime; + using System.Net.Sockets; using System.Runtime.Serialization.Json; using System.Text; using System.Threading; + using System.Threading.Tasks; using System.Web; using System.Xml; using DotNetOpenAuth.Messaging.Reflection; + using Validation; /// <summary> /// Manages sending direct messages to a remote party and receiving responses. /// </summary> [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable.")] - [ContractVerification(true)] - [ContractClass(typeof(ChannelContract))] public abstract class Channel : IDisposable { /// <summary> /// The encoding to use when writing out POST entity strings. @@ -137,31 +140,25 @@ namespace DotNetOpenAuth.Messaging { private IMessageFactory messageTypeProvider; /// <summary> - /// Backing store for the <see cref="CachePolicy"/> property. - /// </summary> - private RequestCachePolicy cachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore); - - /// <summary> /// Backing field for the <see cref="MaximumIndirectMessageUrlLength"/> property. /// </summary> private int maximumIndirectMessageUrlLength = Configuration.DotNetOpenAuthSection.Messaging.MaximumIndirectMessageUrlLength; /// <summary> - /// Initializes a new instance of the <see cref="Channel"/> class. + /// Initializes a new instance of the <see cref="Channel" /> class. /// </summary> - /// <param name="messageTypeProvider"> - /// A class prepared to analyze incoming messages and indicate what concrete - /// message types can deserialize from it. - /// </param> - /// <param name="bindingElements"> - /// The binding elements to use in sending and receiving messages. - /// The order they are provided is used for outgoing messgaes, and reversed for incoming messages. - /// </param> - protected Channel(IMessageFactory messageTypeProvider, params IChannelBindingElement[] bindingElements) { + /// <param name="messageTypeProvider">A class prepared to analyze incoming messages and indicate what concrete + /// message types can deserialize from it.</param> + /// <param name="bindingElements">The binding elements to use in sending and receiving messages. + /// The order they are provided is used for outgoing messgaes, and reversed for incoming messages.</param> + /// <param name="hostFactories">The host factories.</param> + protected Channel(IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements, IHostFactories hostFactories) { Requires.NotNull(messageTypeProvider, "messageTypeProvider"); + Requires.NotNull(bindingElements, "bindingElements"); + Requires.NotNull(hostFactories, "hostFactories"); this.messageTypeProvider = messageTypeProvider; - this.WebRequestHandler = new StandardWebRequestHandler(); + this.HostFactories = hostFactories; this.XmlDictionaryReaderQuotas = DefaultUntrustedXmlDictionaryReaderQuotas; this.outgoingBindingElements = new List<IChannelBindingElement>(ValidateAndPrepareBindingElements(bindingElements)); @@ -179,14 +176,9 @@ namespace DotNetOpenAuth.Messaging { internal event EventHandler<ChannelEventArgs> Sending; /// <summary> - /// Gets or sets an instance to a <see cref="IDirectWebRequestHandler"/> that will be used when - /// submitting HTTP requests and waiting for responses. + /// Gets the host factories instance to use. /// </summary> - /// <remarks> - /// This defaults to a straightforward implementation, but can be set - /// to a mock object for testing purposes. - /// </remarks> - public IDirectWebRequestHandler WebRequestHandler { get; set; } + public IHostFactories HostFactories { get; private set; } /// <summary> /// Gets or sets the maximum allowable size for a 301 Redirect response before we send @@ -201,7 +193,7 @@ namespace DotNetOpenAuth.Messaging { } set { - Requires.InRange(value >= 500 && value <= 4096, "value"); + Requires.Range(value >= 500 && value <= 4096, "value"); this.maximumIndirectMessageUrlLength = value; } } @@ -229,13 +221,28 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Gets or sets the outgoing message filter. + /// </summary> + /// <value> + /// The outgoing message filter. + /// </value> + internal Action<IProtocolMessage> OutgoingMessageFilter { get; set; } + + /// <summary> + /// Gets or sets the incoming message filter. + /// </summary> + /// <value> + /// The incoming message filter. + /// </value> + internal Action<IProtocolMessage> IncomingMessageFilter { get; set; } + + /// <summary> /// Gets the binding elements used by this channel, in no particular guaranteed order. /// </summary> protected internal ReadOnlyCollection<IChannelBindingElement> BindingElements { get { - Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>() != null); var result = this.outgoingBindingElements.AsReadOnly(); - Contract.Assume(result != null); // should be an implicit BCL contract + Assumes.True(result != null); // should be an implicit BCL contract return result; } } @@ -252,8 +259,6 @@ namespace DotNetOpenAuth.Messaging { /// </summary> protected internal ReadOnlyCollection<IChannelBindingElement> IncomingBindingElements { get { - Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>().All(be => be.Channel != null)); - Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>().All(be => be != null)); return this.incomingBindingElements.AsReadOnly(); } } @@ -276,76 +281,25 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Gets or sets the cache policy to use for direct message requests. - /// </summary> - /// <value>Default is <see cref="HttpRequestCacheLevel.NoCacheNoStore"/>.</value> - protected RequestCachePolicy CachePolicy { - get { - return this.cachePolicy; - } - - set { - Requires.NotNull(value, "value"); - this.cachePolicy = value; - } - } - - /// <summary> /// Gets or sets the XML dictionary reader quotas. /// </summary> /// <value>The XML dictionary reader quotas.</value> protected virtual XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas { get; set; } /// <summary> - /// Sends an indirect message (either a request or response) - /// or direct message response for transmission to a remote party - /// and ends execution on the current page or handler. - /// </summary> - /// <param name="message">The one-way message to send</param> - /// <exception cref="ThreadAbortException">Thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> - /// <remarks> - /// Requires an HttpContext.Current context. - /// </remarks> - [EditorBrowsable(EditorBrowsableState.Never)] - public void Send(IProtocolMessage message) { - Requires.ValidState(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); - Requires.NotNull(message, "message"); - this.PrepareResponse(message).Respond(HttpContext.Current, true); - } - - /// <summary> - /// Sends an indirect message (either a request or response) - /// or direct message response for transmission to a remote party - /// and skips most of the remaining ASP.NET request handling pipeline. - /// Not safe to call from ASP.NET web forms. - /// </summary> - /// <param name="message">The one-way message to send</param> - /// <remarks> - /// Requires an HttpContext.Current context. - /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because - /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. - /// Use the <see cref="Send"/> method instead for web forms. - /// </remarks> - public void Respond(IProtocolMessage message) { - Requires.ValidState(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); - Requires.NotNull(message, "message"); - this.PrepareResponse(message).Respond(); - } - - /// <summary> /// Prepares an indirect message (either a request or response) /// or direct message response for transmission to a remote party. /// </summary> /// <param name="message">The one-way message to send</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> - public OutgoingWebResponse PrepareResponse(IProtocolMessage message) { + public async Task<HttpResponseMessage> PrepareResponseAsync(IProtocolMessage message, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(message, "message"); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - this.ProcessOutgoingMessage(message); + await this.ProcessOutgoingMessageAsync(message, cancellationToken); Logger.Channel.DebugFormat("Sending message: {0}", message.GetType().Name); - OutgoingWebResponse result; + HttpResponseMessage result; switch (message.Transport) { case MessageTransport.Direct: // This is a response to a direct message. @@ -374,8 +328,13 @@ namespace DotNetOpenAuth.Messaging { // Apply caching policy to any response. We want to disable all caching because in auth* protocols, // caching can be utilized in identity spoofing attacks. - result.Headers[HttpResponseHeader.CacheControl] = "no-cache, no-store, max-age=0, must-revalidate"; - result.Headers[HttpResponseHeader.Pragma] = "no-cache"; + result.Headers.CacheControl = new CacheControlHeaderValue { + NoCache = true, + NoStore = true, + MaxAge = TimeSpan.Zero, + MustRevalidate = true, + }; + result.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); return result; } @@ -383,71 +342,26 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Gets the protocol message embedded in the given HTTP request, if present. /// </summary> - /// <returns>The deserialized message, if one is found. Null otherwise.</returns> - /// <remarks> - /// Requires an HttpContext.Current context. - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> - public IDirectedProtocolMessage ReadFromRequest() { - return this.ReadFromRequest(this.GetRequestFromContext()); - } - - /// <summary> - /// Gets the protocol message embedded in the given HTTP request, if present. - /// </summary> - /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> - /// <param name="request">The deserialized message, if one is found. Null otherwise.</param> - /// <returns>True if the expected message was recognized and deserialized. False otherwise.</returns> - /// <remarks> - /// Requires an HttpContext.Current context. - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> - /// <exception cref="ProtocolException">Thrown when a request message of an unexpected type is received.</exception> - public bool TryReadFromRequest<TRequest>(out TRequest request) - where TRequest : class, IProtocolMessage { - return TryReadFromRequest<TRequest>(this.GetRequestFromContext(), out request); - } - - /// <summary> - /// Gets the protocol message embedded in the given HTTP request, if present. - /// </summary> /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> /// <param name="httpRequest">The request to search for an embedded message.</param> - /// <param name="request">The deserialized message, if one is found. Null otherwise.</param> - /// <returns>True if the expected message was recognized and deserialized. False otherwise.</returns> - /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// True if the expected message was recognized and deserialized. False otherwise. + /// </returns> + /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current" /> is null.</exception> /// <exception cref="ProtocolException">Thrown when a request message of an unexpected type is received.</exception> - public bool TryReadFromRequest<TRequest>(HttpRequestBase httpRequest, out TRequest request) + public async Task<TRequest> TryReadFromRequestAsync<TRequest>(HttpRequestMessage httpRequest, CancellationToken cancellationToken) where TRequest : class, IProtocolMessage { Requires.NotNull(httpRequest, "httpRequest"); - Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<TRequest>(out request) != null)); - IProtocolMessage untypedRequest = this.ReadFromRequest(httpRequest); + IProtocolMessage untypedRequest = await this.ReadFromRequestAsync(httpRequest, cancellationToken); if (untypedRequest == null) { - request = null; - return false; + return null; } - request = untypedRequest as TRequest; + var request = untypedRequest as TRequest; ErrorUtilities.VerifyProtocol(request != null, MessagingStrings.UnexpectedMessageReceived, typeof(TRequest), untypedRequest.GetType()); - - return true; - } - - /// <summary> - /// Gets the protocol message embedded in the current HTTP request. - /// </summary> - /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> - /// <returns>The deserialized message. Never null.</returns> - /// <remarks> - /// Requires an HttpContext.Current context. - /// </remarks> - /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception> - /// <exception cref="ProtocolException">Thrown if the expected message was not recognized in the response.</exception> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] - public TRequest ReadFromRequest<TRequest>() - where TRequest : class, IProtocolMessage { - return this.ReadFromRequest<TRequest>(this.GetRequestFromContext()); + return request; } /// <summary> @@ -455,43 +369,47 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> /// <param name="httpRequest">The request to search for an embedded message.</param> - /// <returns>The deserialized message. Never null.</returns> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The deserialized message. Never null. + /// </returns> /// <exception cref="ProtocolException">Thrown if the expected message was not recognized in the response.</exception> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] - public TRequest ReadFromRequest<TRequest>(HttpRequestBase httpRequest) + public async Task<TRequest> ReadFromRequestAsync<TRequest>(HttpRequestMessage httpRequest, CancellationToken cancellationToken) where TRequest : class, IProtocolMessage { Requires.NotNull(httpRequest, "httpRequest"); - TRequest request; - if (this.TryReadFromRequest<TRequest>(httpRequest, out request)) { - return request; - } else { - throw ErrorUtilities.ThrowProtocol(MessagingStrings.ExpectedMessageNotReceived, typeof(TRequest)); - } + + TRequest request = await this.TryReadFromRequestAsync<TRequest>(httpRequest, cancellationToken); + ErrorUtilities.VerifyProtocol(request != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TRequest)); + return request; } /// <summary> /// Gets the protocol message that may be embedded in the given HTTP request. /// </summary> /// <param name="httpRequest">The request to search for an embedded message.</param> - /// <returns>The deserialized message, if one is found. Null otherwise.</returns> - public IDirectedProtocolMessage ReadFromRequest(HttpRequestBase httpRequest) { + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The deserialized message, if one is found. Null otherwise. + /// </returns> + public async Task<IDirectedProtocolMessage> ReadFromRequestAsync(HttpRequestMessage httpRequest, CancellationToken cancellationToken) { Requires.NotNull(httpRequest, "httpRequest"); - if (Logger.Channel.IsInfoEnabled && httpRequest.GetPublicFacingUrl() != null) { - Logger.Channel.InfoFormat("Scanning incoming request for messages: {0}", httpRequest.GetPublicFacingUrl().AbsoluteUri); + if (Logger.Channel.IsInfoEnabled && httpRequest.RequestUri != null) { + Logger.Channel.InfoFormat("Scanning incoming request for messages: {0}", httpRequest.RequestUri.AbsoluteUri); } - IDirectedProtocolMessage requestMessage = this.ReadFromRequestCore(httpRequest); + IDirectedProtocolMessage requestMessage = await this.ReadFromRequestCoreAsync(httpRequest, cancellationToken); if (requestMessage != null) { Logger.Channel.DebugFormat("Incoming request received: {0}", requestMessage.GetType().Name); var directRequest = requestMessage as IHttpDirectRequest; if (directRequest != null) { - foreach (string header in httpRequest.Headers) { - directRequest.Headers[header] = httpRequest.Headers[header]; + foreach (var header in httpRequest.Headers) { + directRequest.Headers.Add(header.Key, header.Value); } } - this.ProcessIncomingMessage(requestMessage); + await this.ProcessIncomingMessageAsync(requestMessage, cancellationToken); } return requestMessage; @@ -502,18 +420,18 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <typeparam name="TResponse">The expected type of the message to be received.</typeparam> /// <param name="requestMessage">The message to send.</param> - /// <returns>The remote party's response.</returns> - /// <exception cref="ProtocolException"> - /// Thrown if no message is recognized in the response - /// or an unexpected type of message is received. - /// </exception> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The remote party's response. + /// </returns> + /// <exception cref="ProtocolException">Thrown if no message is recognized in the response + /// or an unexpected type of message is received.</exception> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] - public TResponse Request<TResponse>(IDirectedProtocolMessage requestMessage) + public async Task<TResponse> RequestAsync<TResponse>(IDirectedProtocolMessage requestMessage, CancellationToken cancellationToken) where TResponse : class, IProtocolMessage { Requires.NotNull(requestMessage, "requestMessage"); - Contract.Ensures(Contract.Result<TResponse>() != null); - IProtocolMessage response = this.Request(requestMessage); + IProtocolMessage response = await this.RequestAsync(requestMessage, cancellationToken); ErrorUtilities.VerifyProtocol(response != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TResponse)); var expectedResponse = response as TResponse; @@ -526,18 +444,21 @@ namespace DotNetOpenAuth.Messaging { /// Sends a direct message to a remote party and waits for the response. /// </summary> /// <param name="requestMessage">The message to send.</param> - /// <returns>The remote party's response. Guaranteed to never be null.</returns> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The remote party's response. Guaranteed to never be null. + /// </returns> /// <exception cref="ProtocolException">Thrown if the response does not include a protocol message.</exception> - public IProtocolMessage Request(IDirectedProtocolMessage requestMessage) { + public async Task<IProtocolMessage> RequestAsync(IDirectedProtocolMessage requestMessage, CancellationToken cancellationToken) { Requires.NotNull(requestMessage, "requestMessage"); - this.ProcessOutgoingMessage(requestMessage); + await this.ProcessOutgoingMessageAsync(requestMessage, cancellationToken); Logger.Channel.DebugFormat("Sending {0} request.", requestMessage.GetType().Name); - var responseMessage = this.RequestCore(requestMessage); + var responseMessage = await this.RequestCoreAsync(requestMessage, cancellationToken); ErrorUtilities.VerifyProtocol(responseMessage != null, MessagingStrings.ExpectedMessageNotReceived, typeof(IProtocolMessage).Name); Logger.Channel.DebugFormat("Received {0} response.", responseMessage.GetType().Name); - this.ProcessIncomingMessage(responseMessage); + await this.ProcessIncomingMessageAsync(responseMessage, cancellationToken); return responseMessage; } @@ -558,12 +479,14 @@ namespace DotNetOpenAuth.Messaging { /// Verifies the integrity and applicability of an incoming message. /// </summary> /// <param name="message">The message just received.</param> - /// <exception cref="ProtocolException"> - /// Thrown when the message is somehow invalid. - /// This can be due to tampering, replay attack or expiration, among other things. - /// </exception> - internal void ProcessIncomingMessageTestHook(IProtocolMessage message) { - this.ProcessIncomingMessage(message); + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> + /// <exception cref="ProtocolException">Thrown when the message is somehow invalid. + /// This can be due to tampering, replay attack or expiration, among other things.</exception> + internal Task ProcessIncomingMessageTestHookAsync(IProtocolMessage message, CancellationToken cancellationToken) { + return this.ProcessIncomingMessageAsync(message, cancellationToken); } /// <summary> @@ -572,10 +495,10 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The message to send.</param> /// <returns>The <see cref="HttpWebRequest"/> prepared to send the request.</returns> /// <remarks> - /// This method must be overridden by a derived class, unless the <see cref="RequestCore"/> method + /// This method must be overridden by a derived class, unless the <see cref="RequestCoreAsync"/> method /// is overridden and does not require this method. /// </remarks> - internal HttpWebRequest CreateHttpRequestTestHook(IDirectedProtocolMessage request) { + internal HttpRequestMessage CreateHttpRequestTestHook(IDirectedProtocolMessage request) { return this.CreateHttpRequest(request); } @@ -588,7 +511,7 @@ namespace DotNetOpenAuth.Messaging { /// <remarks> /// This method implements spec OAuth V1.0 section 5.3. /// </remarks> - internal OutgoingWebResponse PrepareDirectResponseTestHook(IProtocolMessage response) { + internal HttpResponseMessage PrepareDirectResponseTestHook(IProtocolMessage response) { return this.PrepareDirectResponse(response); } @@ -596,22 +519,44 @@ namespace DotNetOpenAuth.Messaging { /// Gets the protocol message that may be in the given HTTP response. /// </summary> /// <param name="response">The response that is anticipated to contain an protocol message.</param> - /// <returns>The deserialized message parts, if found. Null otherwise.</returns> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The deserialized message parts, if found. Null otherwise. + /// </returns> /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> - internal IDictionary<string, string> ReadFromResponseCoreTestHook(IncomingWebResponse response) { - return this.ReadFromResponseCore(response); + internal Task<IDictionary<string, string>> ReadFromResponseCoreAsyncTestHook(HttpResponseMessage response, CancellationToken cancellationToken) { + return this.ReadFromResponseCoreAsync(response, cancellationToken); } - /// <remarks> - /// This method should NOT be called by derived types - /// except when sending ONE WAY request messages. - /// </remarks> /// <summary> /// Prepares a message for transmit by applying signatures, nonces, etc. /// </summary> /// <param name="message">The message to prepare for sending.</param> - internal void ProcessOutgoingMessageTestHook(IProtocolMessage message) { - this.ProcessOutgoingMessage(message); + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> + /// <remarks> + /// This method should NOT be called by derived types + /// except when sending ONE WAY request messages. + /// </remarks> + internal Task ProcessOutgoingMessageTestHookAsync(IProtocolMessage message, CancellationToken cancellationToken = default(CancellationToken)) { + return this.ProcessOutgoingMessageAsync(message, cancellationToken); + } + + /// <summary> + /// Parses the URL encoded form content. + /// </summary> + /// <param name="request">The request.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>A sequence of key=value pairs found in the request's entity; or an empty sequence if none are found.</returns> + protected internal static async Task<IEnumerable<KeyValuePair<string, string>>> ParseUrlEncodedFormContentAsync(HttpRequestMessage request, CancellationToken cancellationToken) { + if (request.Content != null && request.Content.Headers.ContentType != null + && request.Content.Headers.ContentType.MediaType.Equals(HttpFormUrlEncoded)) { + return HttpUtility.ParseQueryString(await request.Content.ReadAsStringAsync()).AsKeyValuePairs(); + } + + return Enumerable.Empty<KeyValuePair<string, string>>(); } /// <summary> @@ -620,7 +565,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>An HttpContextBase instance.</returns> [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Allocates memory")] protected internal virtual HttpContextBase GetHttpContext() { - Requires.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); + RequiresEx.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); return new HttpContextWrapper(HttpContext.Current); } @@ -634,28 +579,51 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly call should not be a property.")] protected internal virtual HttpRequestBase GetRequestFromContext() { - Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - Contract.Ensures(Contract.Result<HttpRequestBase>() != null); + RequiresEx.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - Contract.Assume(HttpContext.Current.Request.Url != null); - Contract.Assume(HttpContext.Current.Request.RawUrl != null); + Assumes.True(HttpContext.Current.Request.Url != null); + Assumes.True(HttpContext.Current.Request.RawUrl != null); return new HttpRequestWrapper(HttpContext.Current.Request); } /// <summary> + /// Adds just the binary data part of a message to a multipart form content object. + /// </summary> + /// <param name="requestMessageWithBinaryData">The request message with binary data.</param> + /// <returns>The initialized HttpContent.</returns> + protected static MultipartFormDataContent InitializeMultipartFormDataContent(IMessageWithBinaryData requestMessageWithBinaryData) { + Requires.NotNull(requestMessageWithBinaryData, "requestMessageWithBinaryData"); + + var content = new MultipartFormDataContent(); + foreach (var part in requestMessageWithBinaryData.BinaryData) { + if (string.IsNullOrEmpty(part.Name)) { + content.Add(part.Content); + } else if (string.IsNullOrEmpty(part.FileName)) { + content.Add(part.Content, part.Name); + } else { + content.Add(part.Content, part.Name, part.FileName); + } + } + + return content; + } + + /// <summary> /// Checks whether a given HTTP method is expected to include an entity body in its request. /// </summary> /// <param name="httpMethod">The HTTP method.</param> /// <returns><c>true</c> if the HTTP method is supposed to have an entity; <c>false</c> otherwise.</returns> - protected static bool HttpMethodHasEntity(string httpMethod) { - if (string.Equals(httpMethod, "GET", StringComparison.Ordinal) || - string.Equals(httpMethod, "HEAD", StringComparison.Ordinal) || - string.Equals(httpMethod, "DELETE", StringComparison.Ordinal) || - string.Equals(httpMethod, "OPTIONS", StringComparison.Ordinal)) { + protected static bool HttpMethodHasEntity(HttpMethod httpMethod) { + Requires.NotNull(httpMethod, "httpMethod"); + + if (httpMethod == HttpMethod.Get || + httpMethod == HttpMethod.Head || + httpMethod == HttpMethod.Delete || + httpMethod == HttpMethod.Options) { return false; - } else if (string.Equals(httpMethod, "POST", StringComparison.Ordinal) || - string.Equals(httpMethod, "PUT", StringComparison.Ordinal) || - string.Equals(httpMethod, "PATCH", StringComparison.Ordinal)) { + } else if (httpMethod == HttpMethod.Post || + httpMethod == HttpMethod.Put || + string.Equals(httpMethod.Method, "PATCH", StringComparison.Ordinal)) { return true; } else { throw ErrorUtilities.ThrowArgumentNamed("httpMethod", MessagingStrings.UnsupportedHttpVerb, httpMethod); @@ -667,11 +635,11 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="message">The message.</param> /// <param name="response">The HTTP response.</param> - protected static void ApplyMessageTemplate(IMessage message, OutgoingWebResponse response) { + protected static void ApplyMessageTemplate(IMessage message, HttpResponseMessage response) { Requires.NotNull(message, "message"); var httpMessage = message as IHttpDirectResponse; if (httpMessage != null) { - response.Status = httpMessage.HttpStatusCode; + response.StatusCode = httpMessage.HttpStatusCode; foreach (string headerName in httpMessage.Headers) { response.Headers.Add(headerName, httpMessage.Headers[headerName]); } @@ -707,63 +675,63 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Gets the direct response of a direct HTTP request. - /// </summary> - /// <param name="webRequest">The web request.</param> - /// <returns>The response to the web request.</returns> - /// <exception cref="ProtocolException">Thrown on network or protocol errors.</exception> - protected virtual IncomingWebResponse GetDirectResponse(HttpWebRequest webRequest) { - Requires.NotNull(webRequest, "webRequest"); - return this.WebRequestHandler.GetResponse(webRequest); - } - - /// <summary> /// Submits a direct request message to some remote party and blocks waiting for an immediately reply. /// </summary> /// <param name="request">The request message.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The response message, or null if the response did not carry a message.</returns> /// <remarks> /// Typically a deriving channel will override <see cref="CreateHttpRequest"/> to customize this method's /// behavior. However in non-HTTP frameworks, such as unit test mocks, it may be appropriate to override /// this method to eliminate all use of an HTTP transport. /// </remarks> - protected virtual IProtocolMessage RequestCore(IDirectedProtocolMessage request) { + protected virtual async Task<IProtocolMessage> RequestCoreAsync(IDirectedProtocolMessage request, CancellationToken cancellationToken) { Requires.NotNull(request, "request"); - Requires.True(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient); + Requires.That(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient); - HttpWebRequest webRequest = this.CreateHttpRequest(request); + if (this.OutgoingMessageFilter != null) { + this.OutgoingMessageFilter(request); + } + + var webRequest = this.CreateHttpRequest(request); var directRequest = request as IHttpDirectRequest; if (directRequest != null) { - foreach (string header in directRequest.Headers) { - webRequest.Headers[header] = directRequest.Headers[header]; + foreach (var header in directRequest.Headers) { + webRequest.Headers.Add(header.Key, header.Value); } } - IDictionary<string, string> responseFields; - IDirectResponseProtocolMessage responseMessage; - - using (IncomingWebResponse response = this.GetDirectResponse(webRequest)) { - if (response.ResponseStream == null) { - return null; - } - - responseFields = this.ReadFromResponseCore(response); - if (responseFields == null) { - return null; - } - - responseMessage = this.MessageFactory.GetNewResponseMessage(request, responseFields); - if (responseMessage == null) { - return null; + try { + using (var httpClient = this.HostFactories.CreateHttpClient()) { + using (var response = await httpClient.SendAsync(webRequest, cancellationToken)) { + if (response.Content != null) { + var responseFields = await this.ReadFromResponseCoreAsync(response, cancellationToken); + if (responseFields != null) { + var responseMessage = this.MessageFactory.GetNewResponseMessage(request, responseFields); + if (responseMessage != null) { + this.OnReceivingDirectResponse(response, responseMessage); + + var messageAccessor = this.MessageDescriptions.GetAccessor(responseMessage); + messageAccessor.Deserialize(responseFields); + + return responseMessage; + } + } + } + + if (!response.IsSuccessStatusCode) { + var errorContent = (response.Content != null) ? await response.Content.ReadAsStringAsync() : null; + Logger.Http.ErrorFormat( + "Error received in HTTP response: {0} {1}\n{2}", (int)response.StatusCode, response.ReasonPhrase, errorContent); + response.EnsureSuccessStatusCode(); // throw so we can wrap it in our catch block. + } + + return null; + } } - - this.OnReceivingDirectResponse(response, responseMessage); + } catch (HttpRequestException requestException) { + throw ErrorUtilities.Wrap(requestException, "Error sending HTTP request or receiving response."); } - - var messageAccessor = this.MessageDescriptions.GetAccessor(responseMessage); - messageAccessor.Deserialize(responseFields); - - return responseMessage; } /// <summary> @@ -771,24 +739,27 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="response">The HTTP direct response.</param> /// <param name="message">The newly instantiated message, prior to deserialization.</param> - protected virtual void OnReceivingDirectResponse(IncomingWebResponse response, IDirectResponseProtocolMessage message) { + protected virtual void OnReceivingDirectResponse(HttpResponseMessage response, IDirectResponseProtocolMessage message) { } /// <summary> /// Gets the protocol message that may be embedded in the given HTTP request. /// </summary> /// <param name="request">The request to search for an embedded message.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The deserialized message, if one is found. Null otherwise.</returns> - protected virtual IDirectedProtocolMessage ReadFromRequestCore(HttpRequestBase request) { + protected virtual async Task<IDirectedProtocolMessage> ReadFromRequestCoreAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Requires.NotNull(request, "request"); - Logger.Channel.DebugFormat("Incoming HTTP request: {0} {1}", request.HttpMethod, request.GetPublicFacingUrl().AbsoluteUri); + Logger.Channel.DebugFormat("Incoming HTTP request: {0} {1}", request.Method, request.RequestUri.AbsoluteUri); + + var fields = new Dictionary<string, string>(); // Search Form data first, and if nothing is there search the QueryString - Contract.Assume(request.Form != null && request.GetQueryStringBeforeRewriting() != null); - var fields = request.Form.ToDictionary(); - if (fields.Count == 0 && request.HttpMethod != "POST") { // OpenID 2.0 section 4.1.2 - fields = request.GetQueryStringBeforeRewriting().ToDictionary(); + fields.AddRange(await ParseUrlEncodedFormContentAsync(request, cancellationToken)); + + if (fields.Count == 0 && request.Method.Method != "POST") { // OpenID 2.0 section 4.1.2 + fields.AddRange(HttpUtility.ParseQueryString(request.RequestUri.Query).AsKeyValuePairs()); } MessageReceivingEndpoint recipient; @@ -835,18 +806,17 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="message">The message to send.</param> /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> - protected virtual OutgoingWebResponse PrepareIndirectResponse(IDirectedProtocolMessage message) { + protected virtual HttpResponseMessage PrepareIndirectResponse(IDirectedProtocolMessage message) { Requires.NotNull(message, "message"); - Requires.True(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); - Requires.True((message.HttpMethods & (HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest)) != 0, "message"); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + Requires.That(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); + Requires.That((message.HttpMethods & (HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest)) != 0, "message", "GET or POST expected."); - Contract.Assert(message != null && message.Recipient != null); + Assumes.True(message != null && message.Recipient != null); var messageAccessor = this.MessageDescriptions.GetAccessor(message); - Contract.Assert(message != null && message.Recipient != null); + Assumes.True(message != null && message.Recipient != null); var fields = messageAccessor.Serialize(); - OutgoingWebResponse response = null; + HttpResponseMessage response = null; bool tooLargeForGet = false; if ((message.HttpMethods & HttpDeliveryMethods.GetRequest) == HttpDeliveryMethods.GetRequest) { bool payloadInFragment = false; @@ -858,7 +828,7 @@ namespace DotNetOpenAuth.Messaging { // First try creating a 301 redirect, and fallback to a form POST // if the message is too big. response = this.Create301RedirectResponse(message, fields, payloadInFragment); - tooLargeForGet = response.Headers[HttpResponseHeader.Location].Length > this.MaximumIndirectMessageUrlLength; + tooLargeForGet = response.Headers.Location.PathAndQuery.Length > this.MaximumIndirectMessageUrlLength; } // Make sure that if the message is too large for GET that POST is allowed. @@ -885,15 +855,13 @@ namespace DotNetOpenAuth.Messaging { /// <param name="payloadInFragment">if set to <c>true</c> the redirect will contain the message payload in the #fragment portion of the URL rather than the ?querystring.</param> /// <returns>The encoded HTTP response.</returns> [Pure] - protected virtual OutgoingWebResponse Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields, bool payloadInFragment = false) { + protected virtual HttpResponseMessage Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields, bool payloadInFragment = false) { Requires.NotNull(message, "message"); - Requires.True(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); + Requires.That(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); Requires.NotNull(fields, "fields"); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); // As part of this redirect, we include an HTML body in order to get passed some proxy filters // such as WebSense. - WebHeaderCollection headers = new WebHeaderCollection(); UriBuilder builder = new UriBuilder(message.Recipient); if (payloadInFragment) { builder.AppendFragmentArgs(fields); @@ -901,16 +869,14 @@ namespace DotNetOpenAuth.Messaging { builder.AppendQueryArgs(fields); } - headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri); - headers.Add(HttpResponseHeader.ContentType, "text/html; charset=utf-8"); Logger.Http.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri); - OutgoingWebResponse response = new OutgoingWebResponse { - Status = HttpStatusCode.Redirect, - Headers = headers, - Body = string.Format(CultureInfo.InvariantCulture, RedirectResponseBodyFormat, builder.Uri.AbsoluteUri), - OriginalMessage = message + HttpResponseMessage response = new HttpResponseMessageWithOriginal(message) { + StatusCode = HttpStatusCode.Redirect, + Content = new StringContent(string.Format(CultureInfo.InvariantCulture, RedirectResponseBodyFormat, builder.Uri.AbsoluteUri)), }; + response.Headers.Location = builder.Uri; + response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html") { CharSet = "utf-8" }; return response; } @@ -922,14 +888,11 @@ namespace DotNetOpenAuth.Messaging { /// <param name="fields">The pre-serialized fields from the message.</param> /// <returns>The encoded HTTP response.</returns> [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")] - protected virtual OutgoingWebResponse CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { + protected virtual HttpResponseMessage CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { Requires.NotNull(message, "message"); - Requires.True(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); + Requires.That(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); Requires.NotNull(fields, "fields"); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - WebHeaderCollection headers = new WebHeaderCollection(); - headers.Add(HttpResponseHeader.ContentType, "text/html"); using (StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture)) { StringBuilder hiddenFields = new StringBuilder(); foreach (var field in fields) { @@ -943,12 +906,11 @@ namespace DotNetOpenAuth.Messaging { HttpUtility.HtmlEncode(message.Recipient.AbsoluteUri), hiddenFields); bodyWriter.Flush(); - OutgoingWebResponse response = new OutgoingWebResponse { - Status = HttpStatusCode.OK, - Headers = headers, - Body = bodyWriter.ToString(), - OriginalMessage = message + HttpResponseMessage response = new HttpResponseMessageWithOriginal(message) { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(bodyWriter.ToString()), }; + response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html"); return response; } @@ -958,9 +920,12 @@ namespace DotNetOpenAuth.Messaging { /// Gets the protocol message that may be in the given HTTP response. /// </summary> /// <param name="response">The response that is anticipated to contain an protocol message.</param> - /// <returns>The deserialized message parts, if found. Null otherwise.</returns> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The deserialized message parts, if found. Null otherwise. + /// </returns> /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> - protected abstract IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response); + protected abstract Task<IDictionary<string, string>> ReadFromResponseCoreAsync(HttpResponseMessage response, CancellationToken cancellationToken); /// <summary> /// Prepares an HTTP request that carries a given message. @@ -968,13 +933,12 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The message to send.</param> /// <returns>The <see cref="HttpWebRequest"/> prepared to send the request.</returns> /// <remarks> - /// This method must be overridden by a derived class, unless the <see cref="Channel.RequestCore"/> method + /// This method must be overridden by a derived class, unless the <see cref="Channel.RequestCoreAsync"/> method /// is overridden and does not require this method. /// </remarks> - protected virtual HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { + protected virtual HttpRequestMessage CreateHttpRequest(IDirectedProtocolMessage request) { Requires.NotNull(request, "request"); - Requires.True(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient); - Contract.Ensures(Contract.Result<HttpWebRequest>() != null); + Requires.That(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient); throw new NotImplementedException(); } @@ -987,7 +951,7 @@ namespace DotNetOpenAuth.Messaging { /// <remarks> /// This method implements spec OAuth V1.0 section 5.3. /// </remarks> - protected abstract OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response); + protected abstract HttpResponseMessage PrepareDirectResponse(IProtocolMessage response); /// <summary> /// Serializes the given message as a JSON string. @@ -1021,11 +985,16 @@ namespace DotNetOpenAuth.Messaging { /// Prepares a message for transmit by applying signatures, nonces, etc. /// </summary> /// <param name="message">The message to prepare for sending.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> + /// <exception cref="UnprotectedMessageException">Thrown if the message does not have the minimal required protections applied.</exception> /// <remarks> /// This method should NOT be called by derived types /// except when sending ONE WAY request messages. /// </remarks> - protected void ProcessOutgoingMessage(IProtocolMessage message) { + protected async Task ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { Requires.NotNull(message, "message"); Logger.Channel.DebugFormat("Preparing to send {0} ({1}) message.", message.GetType().Name, message.Version); @@ -1039,8 +1008,8 @@ namespace DotNetOpenAuth.Messaging { MessageProtections appliedProtection = MessageProtections.None; foreach (IChannelBindingElement bindingElement in this.outgoingBindingElements) { - Contract.Assume(bindingElement.Channel != null); - MessageProtections? elementProtection = bindingElement.ProcessOutgoingMessage(message); + Assumes.True(bindingElement.Channel != null); + MessageProtections? elementProtection = await bindingElement.ProcessOutgoingMessageAsync(message, cancellationToken); if (elementProtection.HasValue) { Logger.Bindings.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName); @@ -1061,6 +1030,10 @@ namespace DotNetOpenAuth.Messaging { this.EnsureValidMessageParts(message); message.EnsureValidMessage(); + if (this.OutgoingMessageFilter != null) { + this.OutgoingMessageFilter(message); + } + if (Logger.Channel.IsInfoEnabled) { var directedMessage = message as IDirectedProtocolMessage; string recipient = (directedMessage != null && directedMessage.Recipient != null) ? directedMessage.Recipient.AbsoluteUri : "<response>"; @@ -1084,16 +1057,16 @@ namespace DotNetOpenAuth.Messaging { /// This method is simply a standard HTTP Get request with the message parts serialized to the query string. /// This method satisfies OAuth 1.0 section 5.2, item #3. /// </remarks> - protected virtual HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) { + protected virtual HttpRequestMessage InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) { Requires.NotNull(requestMessage, "requestMessage"); - Requires.True(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient); + Requires.That(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient); var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage); var fields = messageAccessor.Serialize(); UriBuilder builder = new UriBuilder(requestMessage.Recipient); MessagingUtilities.AppendQueryArgs(builder, fields); - HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri); + var httpRequest = new HttpRequestMessage(HttpMethod.Get, builder.Uri); this.PrepareHttpWebRequest(httpRequest); return httpRequest; @@ -1108,12 +1081,12 @@ namespace DotNetOpenAuth.Messaging { /// This method is simply a standard HTTP HEAD request with the message parts serialized to the query string. /// This method satisfies OAuth 1.0 section 5.2, item #3. /// </remarks> - protected virtual HttpWebRequest InitializeRequestAsHead(IDirectedProtocolMessage requestMessage) { + protected virtual HttpRequestMessage InitializeRequestAsHead(IDirectedProtocolMessage requestMessage) { Requires.NotNull(requestMessage, "requestMessage"); - Requires.True(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient); + Requires.That(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient); - HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); - request.Method = "HEAD"; + var request = this.InitializeRequestAsGet(requestMessage); + request.Method = HttpMethod.Head; return request; } @@ -1127,28 +1100,28 @@ namespace DotNetOpenAuth.Messaging { /// with the application/x-www-form-urlencoded content type /// This method satisfies OAuth 1.0 section 5.2, item #2 and OpenID 2.0 section 4.1.2. /// </remarks> - protected virtual HttpWebRequest InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) { + protected virtual HttpRequestMessage InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) { Requires.NotNull(requestMessage, "requestMessage"); - Contract.Ensures(Contract.Result<HttpWebRequest>() != null); var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage); var fields = messageAccessor.Serialize(); - var httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient); + var httpRequest = new HttpRequestMessage(HttpMethod.Post, requestMessage.Recipient); this.PrepareHttpWebRequest(httpRequest); - httpRequest.CachePolicy = this.CachePolicy; - httpRequest.Method = "POST"; var requestMessageWithBinaryData = requestMessage as IMessageWithBinaryData; if (requestMessageWithBinaryData != null && requestMessageWithBinaryData.SendAsMultipart) { - var multiPartFields = new List<MultipartPostPart>(requestMessageWithBinaryData.BinaryData); + var content = InitializeMultipartFormDataContent(requestMessageWithBinaryData); // When sending multi-part, all data gets send as multi-part -- even the non-binary data. - multiPartFields.AddRange(fields.Select(field => MultipartPostPart.CreateFormPart(field.Key, field.Value))); - this.SendParametersInEntityAsMultipart(httpRequest, multiPartFields); + foreach (var field in fields) { + content.Add(new StringContent(field.Value), field.Key); + } + + httpRequest.Content = content; } else { ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || requestMessageWithBinaryData.BinaryData.Count == 0, MessagingStrings.BinaryDataRequiresMultipart); - this.SendParametersInEntity(httpRequest, fields); + httpRequest.Content = new FormUrlEncodedContent(fields); } return httpRequest; @@ -1162,12 +1135,11 @@ namespace DotNetOpenAuth.Messaging { /// <remarks> /// This method is simply a standard HTTP PUT request with the message parts serialized to the query string. /// </remarks> - protected virtual HttpWebRequest InitializeRequestAsPut(IDirectedProtocolMessage requestMessage) { + protected virtual HttpRequestMessage InitializeRequestAsPut(IDirectedProtocolMessage requestMessage) { Requires.NotNull(requestMessage, "requestMessage"); - Contract.Ensures(Contract.Result<HttpWebRequest>() != null); - HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); - request.Method = "PUT"; + var request = this.InitializeRequestAsGet(requestMessage); + request.Method = HttpMethod.Put; return request; } @@ -1179,67 +1151,26 @@ namespace DotNetOpenAuth.Messaging { /// <remarks> /// This method is simply a standard HTTP DELETE request with the message parts serialized to the query string. /// </remarks> - protected virtual HttpWebRequest InitializeRequestAsDelete(IDirectedProtocolMessage requestMessage) { + protected virtual HttpRequestMessage InitializeRequestAsDelete(IDirectedProtocolMessage requestMessage) { Requires.NotNull(requestMessage, "requestMessage"); - Contract.Ensures(Contract.Result<HttpWebRequest>() != null); - HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); - request.Method = "DELETE"; + var request = this.InitializeRequestAsGet(requestMessage); + request.Method = HttpMethod.Delete; return request; } /// <summary> - /// Sends the given parameters in the entity stream of an HTTP request. - /// </summary> - /// <param name="httpRequest">The HTTP request.</param> - /// <param name="fields">The parameters to send.</param> - /// <remarks> - /// This method calls <see cref="HttpWebRequest.GetRequestStream()"/> and closes - /// the request stream, but does not call <see cref="HttpWebRequest.GetResponse"/>. - /// </remarks> - protected void SendParametersInEntity(HttpWebRequest httpRequest, IDictionary<string, string> fields) { - Requires.NotNull(httpRequest, "httpRequest"); - Requires.NotNull(fields, "fields"); - - string requestBody = MessagingUtilities.CreateQueryString(fields); - byte[] requestBytes = PostEntityEncoding.GetBytes(requestBody); - httpRequest.ContentType = HttpFormUrlEncodedContentType.ToString(); - httpRequest.ContentLength = requestBytes.Length; - Stream requestStream = this.WebRequestHandler.GetRequestStream(httpRequest); - try { - requestStream.Write(requestBytes, 0, requestBytes.Length); - } finally { - // We need to be sure to close the request stream... - // unless it is a MemoryStream, which is a clue that we're in - // a mock stream situation and closing it would preclude reading it later. - if (!(requestStream is MemoryStream)) { - requestStream.Dispose(); - } - } - } - - /// <summary> - /// Sends the given parameters in the entity stream of an HTTP request in multi-part format. - /// </summary> - /// <param name="httpRequest">The HTTP request.</param> - /// <param name="fields">The parameters to send.</param> - /// <remarks> - /// This method calls <see cref="HttpWebRequest.GetRequestStream()"/> and closes - /// the request stream, but does not call <see cref="HttpWebRequest.GetResponse"/>. - /// </remarks> - protected void SendParametersInEntityAsMultipart(HttpWebRequest httpRequest, IEnumerable<MultipartPostPart> fields) { - httpRequest.PostMultipartNoGetResponse(this.WebRequestHandler, fields); - } - - /// <summary> /// Verifies the integrity and applicability of an incoming message. /// </summary> /// <param name="message">The message just received.</param> - /// <exception cref="ProtocolException"> - /// Thrown when the message is somehow invalid. - /// This can be due to tampering, replay attack or expiration, among other things. - /// </exception> - protected virtual void ProcessIncomingMessage(IProtocolMessage message) { + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> + /// <exception cref="UnprotectedMessageException">Thrown if the message does not have the minimal required protections applied.</exception> + /// <exception cref="ProtocolException">Thrown when the message is somehow invalid. + /// This can be due to tampering, replay attack or expiration, among other things.</exception> + protected virtual async Task ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { Requires.NotNull(message, "message"); if (Logger.Channel.IsInfoEnabled) { @@ -1252,10 +1183,14 @@ namespace DotNetOpenAuth.Messaging { messageAccessor.ToStringDeferred()); } + if (this.IncomingMessageFilter != null) { + this.IncomingMessageFilter(message); + } + MessageProtections appliedProtection = MessageProtections.None; foreach (IChannelBindingElement bindingElement in this.IncomingBindingElements) { - Contract.Assume(bindingElement.Channel != null); // CC bug: this.IncomingBindingElements ensures this... why must we assume it here? - MessageProtections? elementProtection = bindingElement.ProcessIncomingMessage(message); + Assumes.True(bindingElement.Channel != null); // CC bug: this.IncomingBindingElements ensures this... why must we assume it here? + MessageProtections? elementProtection = await bindingElement.ProcessIncomingMessageAsync(message, cancellationToken); if (elementProtection.HasValue) { Logger.Bindings.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName); @@ -1316,7 +1251,7 @@ namespace DotNetOpenAuth.Messaging { /// Performs additional processing on an outgoing web request before it is sent to the remote server. /// </summary> /// <param name="request">The request.</param> - protected virtual void PrepareHttpWebRequest(HttpWebRequest request) { + protected virtual void PrepareHttpWebRequest(HttpRequestMessage request) { Requires.NotNull(request, "request"); } @@ -1350,8 +1285,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The properly ordered list of elements.</returns> /// <exception cref="ProtocolException">Thrown when the binding elements are incomplete or inconsistent with each other.</exception> private static IEnumerable<IChannelBindingElement> ValidateAndPrepareBindingElements(IEnumerable<IChannelBindingElement> elements) { - Requires.NullOrWithNoNullElements(elements, "elements"); - Contract.Ensures(Contract.Result<IEnumerable<IChannelBindingElement>>() != null); + Requires.NullOrNotNullElements(elements, "elements"); if (elements == null) { return new IChannelBindingElement[0]; } @@ -1402,18 +1336,6 @@ namespace DotNetOpenAuth.Messaging { return -((int)protection1).CompareTo((int)protection2); // descending flag ordinal order } -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.MessageDescriptions != null); - } -#endif - /// <summary> /// Verifies that all required message parts are initialized to values /// prior to sending the message to a remote party. diff --git a/src/DotNetOpenAuth.Core/Messaging/ChannelContract.cs b/src/DotNetOpenAuth.Core/Messaging/ChannelContract.cs deleted file mode 100644 index b48d45b..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/ChannelContract.cs +++ /dev/null @@ -1,54 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="ChannelContract.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - - /// <summary> - /// Code contract for the <see cref="Channel"/> class. - /// </summary> - [ContractClassFor(typeof(Channel))] - internal abstract class ChannelContract : Channel { - /// <summary> - /// Prevents a default instance of the ChannelContract class from being created. - /// </summary> - private ChannelContract() - : base(null, null) { - } - - /// <summary> - /// Gets the protocol message that may be in the given HTTP response. - /// </summary> - /// <param name="response">The response that is anticipated to contain an protocol message.</param> - /// <returns> - /// The deserialized message parts, if found. Null otherwise. - /// </returns> - /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> - protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { - Requires.NotNull(response, "response"); - throw new NotImplementedException(); - } - - /// <summary> - /// Queues a message for sending in the response stream where the fields - /// are sent in the response stream in querystring style. - /// </summary> - /// <param name="response">The message to send as a response.</param> - /// <returns> - /// The pending user agent redirect based message to be sent as an HttpResponse. - /// </returns> - /// <remarks> - /// This method implements spec V1.0 section 5.3. - /// </remarks> - protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { - Requires.NotNull(response, "response"); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/ChannelEventArgs.cs b/src/DotNetOpenAuth.Core/Messaging/ChannelEventArgs.cs index f3ebc04..5c69e4d 100644 --- a/src/DotNetOpenAuth.Core/Messaging/ChannelEventArgs.cs +++ b/src/DotNetOpenAuth.Core/Messaging/ChannelEventArgs.cs @@ -6,7 +6,7 @@ namespace DotNetOpenAuth.Messaging { using System; - using System.Diagnostics.Contracts; + using Validation; /// <summary> /// The data packet sent with Channel events. diff --git a/src/DotNetOpenAuth.Core/Messaging/DataBag.cs b/src/DotNetOpenAuth.Core/Messaging/DataBag.cs index 0800840..8469676 100644 --- a/src/DotNetOpenAuth.Core/Messaging/DataBag.cs +++ b/src/DotNetOpenAuth.Core/Messaging/DataBag.cs @@ -8,7 +8,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; + using Validation; /// <summary> /// A collection of message parts that will be serialized into a single string, @@ -42,7 +42,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="version">The DataBag version.</param> protected DataBag(Version version) { - Contract.Requires(version != null); + Requires.NotNull(version, "version"); this.version = version; } diff --git a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs index e7ac254..210a95e 100644 --- a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs +++ b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs @@ -8,7 +8,6 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -17,6 +16,7 @@ namespace DotNetOpenAuth.Messaging { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.Messaging.Reflection; + using Validation; /// <summary> /// A serializer for <see cref="DataBag"/>-derived types @@ -110,8 +110,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> protected DataBagFormatterBase(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) : this(signed, encrypted, compressed, maximumAge, decodeOnceOnly) { - Requires.True(!string.IsNullOrEmpty(bucket) || cryptoKeyStore == null, null); - Requires.True(cryptoKeyStore != null || (!signed && !encrypted), null); + Requires.That(!string.IsNullOrEmpty(bucket) || cryptoKeyStore == null, "bucket", "Bucket name required when cryptoKeyStore is non-null."); + Requires.That(cryptoKeyStore != null || (!signed && !encrypted), "cryptoKeyStore", "cryptoKeyStore required if signing or encrypting."); this.cryptoKeyStore = cryptoKeyStore; this.cryptoKeyBucket = bucket; @@ -129,8 +129,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> private DataBagFormatterBase(bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) { - Requires.True(signed || decodeOnceOnly == null, null); - Requires.True(maximumAge.HasValue || decodeOnceOnly == null, null); + Requires.That(signed || decodeOnceOnly == null, "decodeOnceOnly", "Nonce only valid with signing."); + Requires.That(maximumAge.HasValue || decodeOnceOnly == null, "decodeOnceOnly", "Nonce requires a maximum message age."); this.signed = signed; this.maximumAge = maximumAge; @@ -303,8 +303,7 @@ namespace DotNetOpenAuth.Messaging { [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")] private byte[] CalculateSignature(byte[] bytesToSign, string symmetricSecretHandle) { Requires.NotNull(bytesToSign, "bytesToSign"); - Requires.ValidState(this.asymmetricSigning != null || this.cryptoKeyStore != null); - Contract.Ensures(Contract.Result<byte[]>() != null); + RequiresEx.ValidState(this.asymmetricSigning != null || this.cryptoKeyStore != null); if (this.asymmetricSigning != null) { using (var hasher = SHA1.Create()) { @@ -328,7 +327,7 @@ namespace DotNetOpenAuth.Messaging { /// The encrypted value. /// </returns> private byte[] Encrypt(byte[] value, out string symmetricSecretHandle) { - Requires.ValidState(this.asymmetricEncrypting != null || this.cryptoKeyStore != null); + Assumes.True(this.asymmetricEncrypting != null || this.cryptoKeyStore != null); if (this.asymmetricEncrypting != null) { symmetricSecretHandle = null; @@ -349,7 +348,7 @@ namespace DotNetOpenAuth.Messaging { /// The decrypted value. /// </returns> private byte[] Decrypt(byte[] value, string symmetricSecretHandle) { - Requires.ValidState(this.asymmetricEncrypting != null || symmetricSecretHandle != null); + RequiresEx.ValidState(this.asymmetricEncrypting != null || symmetricSecretHandle != null); if (this.asymmetricEncrypting != null) { return this.asymmetricEncrypting.DecryptWithRandomSymmetricKey(value); diff --git a/src/DotNetOpenAuth.Core/Messaging/EnumerableCacheExtensions.cs b/src/DotNetOpenAuth.Core/Messaging/EnumerableCacheExtensions.cs index 5e9cf93..0886ef2 100644 --- a/src/DotNetOpenAuth.Core/Messaging/EnumerableCacheExtensions.cs +++ b/src/DotNetOpenAuth.Core/Messaging/EnumerableCacheExtensions.cs @@ -9,7 +9,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections; using System.Collections.Generic; - using System.Diagnostics.Contracts; + using Validation; /// <summary> /// Extension methods for <see cref="IEnumerable<T>"/> types. diff --git a/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs index 2237cc7..71c904b 100644 --- a/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs +++ b/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs @@ -11,11 +11,11 @@ namespace DotNetOpenAuth.Messaging { using System.Diagnostics.Contracts; using System.Globalization; using System.Web; + using Validation; /// <summary> /// A collection of error checking and reporting methods. /// </summary> - [ContractVerification(true)] [Pure] internal static class ErrorUtilities { /// <summary> @@ -28,7 +28,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static Exception Wrap(Exception inner, string errorMessage, params object[] args) { Requires.NotNull(args, "args"); - Contract.Assume(errorMessage != null); + Assumes.True(errorMessage != null); return new ProtocolException(string.Format(CultureInfo.CurrentCulture, errorMessage, args), inner); } @@ -58,8 +58,6 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="InternalErrorException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifyInternal(bool condition, string errorMessage) { - Contract.Ensures(condition); - Contract.EnsuresOnThrow<InternalErrorException>(!condition); if (!condition) { ThrowInternal(errorMessage); } @@ -75,9 +73,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static void VerifyInternal(bool condition, string errorMessage, params object[] args) { Requires.NotNull(args, "args"); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<InternalErrorException>(!condition); - Contract.Assume(errorMessage != null); + Assumes.True(errorMessage != null); if (!condition) { errorMessage = string.Format(CultureInfo.CurrentCulture, errorMessage, args); throw new InternalErrorException(errorMessage); @@ -92,8 +88,6 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="InvalidOperationException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifyOperation(bool condition, string errorMessage) { - Contract.Ensures(condition); - Contract.EnsuresOnThrow<InvalidOperationException>(!condition); if (!condition) { throw new InvalidOperationException(errorMessage); } @@ -107,8 +101,6 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="NotSupportedException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifySupported(bool condition, string errorMessage) { - Contract.Ensures(condition); - Contract.EnsuresOnThrow<NotSupportedException>(!condition); if (!condition) { throw new NotSupportedException(errorMessage); } @@ -124,9 +116,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static void VerifySupported(bool condition, string errorMessage, params object[] args) { Requires.NotNull(args, "args"); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<NotSupportedException>(!condition); - Contract.Assume(errorMessage != null); + Assumes.True(errorMessage != null); if (!condition) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, errorMessage, args)); } @@ -142,9 +132,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static void VerifyOperation(bool condition, string errorMessage, params object[] args) { Requires.NotNull(args, "args"); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<InvalidOperationException>(!condition); - Contract.Assume(errorMessage != null); + Assumes.True(errorMessage != null); if (!condition) { errorMessage = string.Format(CultureInfo.CurrentCulture, errorMessage, args); throw new InvalidOperationException(errorMessage); @@ -161,9 +149,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static void VerifyHost(bool condition, string errorMessage, params object[] args) { Requires.NotNull(args, "args"); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<ProtocolException>(!condition); - Contract.Assume(errorMessage != null); + Assumes.True(errorMessage != null); if (!condition) { throw new HostErrorException(string.Format(CultureInfo.CurrentCulture, errorMessage, args)); } @@ -181,9 +167,7 @@ namespace DotNetOpenAuth.Messaging { internal static void VerifyProtocol(bool condition, IProtocolMessage faultedMessage, string errorMessage, params object[] args) { Requires.NotNull(args, "args"); Requires.NotNull(faultedMessage, "faultedMessage"); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<ProtocolException>(!condition); - Contract.Assume(errorMessage != null); + Assumes.True(errorMessage != null); if (!condition) { throw new ProtocolException(string.Format(CultureInfo.CurrentCulture, errorMessage, args), faultedMessage); } @@ -199,9 +183,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static void VerifyProtocol(bool condition, string unformattedMessage, params object[] args) { Requires.NotNull(args, "args"); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<ProtocolException>(!condition); - Contract.Assume(unformattedMessage != null); + Assumes.True(unformattedMessage != null); if (!condition) { var exception = new ProtocolException(string.Format(CultureInfo.CurrentCulture, unformattedMessage, args)); if (Logger.Messaging.IsErrorEnabled) { @@ -231,7 +213,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static Exception ThrowProtocol(string unformattedMessage, params object[] args) { Requires.NotNull(args, "args"); - Contract.Assume(unformattedMessage != null); + Assumes.True(unformattedMessage != null); VerifyProtocol(false, unformattedMessage, args); // we never reach here, but this allows callers to "throw" this method. @@ -247,7 +229,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static Exception ThrowFormat(string message, params object[] args) { Requires.NotNull(args, "args"); - Contract.Assume(message != null); + Assumes.True(message != null); throw new FormatException(string.Format(CultureInfo.CurrentCulture, message, args)); } @@ -261,9 +243,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static void VerifyFormat(bool condition, string message, params object[] args) { Requires.NotNull(args, "args"); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<FormatException>(!condition); - Contract.Assume(message != null); + Assumes.True(message != null); if (!condition) { throw ThrowFormat(message, args); } @@ -279,9 +259,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static void VerifyArgument(bool condition, string message, params object[] args) { Requires.NotNull(args, "args"); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<ArgumentException>(!condition); - Contract.Assume(message != null); + Assumes.True(message != null); if (!condition) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, message, args)); } @@ -297,7 +275,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static Exception ThrowArgumentNamed(string parameterName, string message, params object[] args) { Requires.NotNull(args, "args"); - Contract.Assume(message != null); + Assumes.True(message != null); throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, message, args), parameterName); } @@ -312,9 +290,7 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static void VerifyArgumentNamed(bool condition, string parameterName, string message, params object[] args) { Requires.NotNull(args, "args"); - Contract.Ensures(condition); - Contract.EnsuresOnThrow<ArgumentException>(!condition); - Contract.Assume(message != null); + Assumes.True(message != null); if (!condition) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, message, args), parameterName); } @@ -328,8 +304,6 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> [Pure] internal static void VerifyArgumentNotNull(object value, string paramName) { - Contract.Ensures(value != null); - Contract.EnsuresOnThrow<ArgumentNullException>(value == null); if (value == null) { throw new ArgumentNullException(paramName); } @@ -344,8 +318,6 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ArgumentException">Thrown if <paramref name="value"/> has zero length.</exception> [Pure] internal static void VerifyNonZeroLength(string value, string paramName) { - Contract.Ensures((value != null && value.Length > 0) && !string.IsNullOrEmpty(value)); - Contract.EnsuresOnThrow<ArgumentException>(value == null || value.Length == 0); VerifyArgumentNotNull(value, paramName); if (value.Length == 0) { throw new ArgumentException(MessagingStrings.UnexpectedEmptyString, paramName); @@ -358,8 +330,6 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current"/> == <c>null</c></exception> [Pure] internal static void VerifyHttpContext() { - Contract.Ensures(HttpContext.Current != null); - Contract.Ensures(HttpContext.Current.Request != null); ErrorUtilities.VerifyOperation(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); } diff --git a/src/DotNetOpenAuth.Core/Messaging/HmacAlgorithms.cs b/src/DotNetOpenAuth.Core/Messaging/HmacAlgorithms.cs index 872b4ac..be7e96b 100644 --- a/src/DotNetOpenAuth.Core/Messaging/HmacAlgorithms.cs +++ b/src/DotNetOpenAuth.Core/Messaging/HmacAlgorithms.cs @@ -10,6 +10,7 @@ namespace DotNetOpenAuth.Messaging { using System.Linq; using System.Security.Cryptography; using System.Text; + using Validation; /// <summary> /// HMAC-SHA algorithm names that can be passed to the <see cref="HMAC.Create(string)"/> method. @@ -50,9 +51,7 @@ namespace DotNetOpenAuth.Messaging { hmac.Key = key; return hmac; } catch { -#if CLR4 hmac.Dispose(); -#endif throw; } } diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs index 75da833..ba1faab 100644 --- a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs @@ -9,17 +9,15 @@ namespace DotNetOpenAuth.Messaging { using System.Collections.Specialized; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Net; -#if CLR4 using System.Net.Http; using System.Net.Http.Headers; -#endif using System.Net.Mime; using System.ServiceModel.Channels; using System.Web; + using Validation; /// <summary> /// A property store of details of an incoming HTTP request. @@ -124,7 +122,6 @@ namespace DotNetOpenAuth.Messaging { Reporting.RecordRequestStatistics(this); } -#if CLR4 /// <summary> /// Initializes a new instance of the <see cref="HttpRequestInfo" /> class. /// </summary> @@ -144,7 +141,6 @@ namespace DotNetOpenAuth.Messaging { Reporting.RecordRequestStatistics(this); } -#endif /// <summary> /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. @@ -226,6 +222,58 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// When overridden in a derived class, gets an array of client-supported MIME accept types. + /// </summary> + /// <returns>An array of client-supported MIME accept types.</returns> + public override string[] AcceptTypes { + get { + if (this.Headers["Accept"] != null) { + return this.headers["Accept"].Split(','); + } + + return new string[0]; + } + } + + /// <summary> + /// When overridden in a derived class, gets information about the URL of the client request that linked to the current URL. + /// </summary> + /// <returns>The URL of the page that linked to the current request.</returns> + public override Uri UrlReferrer { + get { + if (this.Headers["Referer"] != null) { // misspelled word intentional, per RFC + return new Uri(this.Headers["Referer"]); + } + + return null; + } + } + + /// <summary> + /// When overridden in a derived class, gets the length, in bytes, of content that was sent by the client. + /// </summary> + /// <returns>The length, in bytes, of content that was sent by the client.</returns> + public override int ContentLength { + get { + if (this.Headers["Content-Length"] != null) { + return int.Parse(this.headers["Content-Length"]); + } + + return 0; + } + } + + /// <summary> + /// When overridden in a derived class, gets or sets the MIME content type of the request. + /// </summary> + /// <returns>The MIME content type of the request, such as "text/html".</returns> + /// <exception cref="System.NotImplementedException">Setter always throws</exception> + public override string ContentType { + get { return this.Headers["Content-Type"]; } + set { throw new NotImplementedException(); } + } + + /// <summary> /// Creates an <see cref="HttpRequestBase"/> instance that describes the specified HTTP request. /// </summary> /// <param name="request">The request.</param> @@ -309,7 +357,6 @@ namespace DotNetOpenAuth.Messaging { return new NameValueCollection(); } -#if CLR4 /// <summary> /// Adds HTTP headers to a <see cref="NameValueCollection"/>. /// </summary> @@ -325,6 +372,5 @@ namespace DotNetOpenAuth.Messaging { } } } -#endif } } diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpResponseMessageWithOriginal.cs b/src/DotNetOpenAuth.Core/Messaging/HttpResponseMessageWithOriginal.cs new file mode 100644 index 0000000..c6d2cc3 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/HttpResponseMessageWithOriginal.cs @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------- +// <copyright file="HttpResponseMessageWithOriginal.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System.Net; + using System.Net.Http; + + using Validation; + + /// <summary> + /// An HttpResponseMessage that includes the original DNOA semantic message as a property. + /// </summary> + /// <remarks> + /// This is used to assist in testing. + /// </remarks> + internal class HttpResponseMessageWithOriginal : HttpResponseMessage { + /// <summary> + /// Initializes a new instance of the <see cref="HttpResponseMessageWithOriginal"/> class. + /// </summary> + /// <param name="originalMessage">The original message.</param> + /// <param name="statusCode">The status code.</param> + internal HttpResponseMessageWithOriginal(IMessage originalMessage, HttpStatusCode statusCode = HttpStatusCode.OK) + : base(statusCode) { + this.OriginalMessage = originalMessage; + Requires.NotNull(originalMessage, "originalMessage"); + } + + /// <summary> + /// Gets the original message. + /// </summary> + internal IMessage OriginalMessage { get; private set; } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/IChannelBindingElement.cs b/src/DotNetOpenAuth.Core/Messaging/IChannelBindingElement.cs index 1047ec5..fe2cf3d 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IChannelBindingElement.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IChannelBindingElement.cs @@ -6,13 +6,14 @@ namespace DotNetOpenAuth.Messaging { using System; - using System.Diagnostics.Contracts; + using System.Threading; + using System.Threading.Tasks; + using Validation; /// <summary> /// An interface that must be implemented by message transforms/validators in order /// to be included in the channel stack. /// </summary> - [ContractClass(typeof(IChannelBindingElementContract))] public interface IChannelBindingElement { /// <summary> /// Gets or sets the channel that this binding element belongs to. @@ -34,6 +35,7 @@ namespace DotNetOpenAuth.Messaging { /// Prepares a message for sending based on the rules of this channel binding element. /// </summary> /// <param name="message">The message to prepare for sending.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. @@ -42,13 +44,14 @@ namespace DotNetOpenAuth.Messaging { /// Implementations that provide message protection must honor the /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> - MessageProtections? ProcessOutgoingMessage(IProtocolMessage message); + Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken); /// <summary> /// Performs any transformation on an incoming message that may be necessary and/or /// validates an incoming message based on the rules of this channel binding element. /// </summary> /// <param name="message">The incoming message to process.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. @@ -61,86 +64,6 @@ namespace DotNetOpenAuth.Messaging { /// Implementations that provide message protection must honor the /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> - MessageProtections? ProcessIncomingMessage(IProtocolMessage message); - } - - /// <summary> - /// Code Contract for the <see cref="IChannelBindingElement"/> interface. - /// </summary> - [ContractClassFor(typeof(IChannelBindingElement))] - internal abstract class IChannelBindingElementContract : IChannelBindingElement { - /// <summary> - /// Prevents a default instance of the <see cref="IChannelBindingElementContract"/> class from being created. - /// </summary> - private IChannelBindingElementContract() { - } - - #region IChannelBindingElement Members - - /// <summary> - /// Gets or sets the channel that this binding element belongs to. - /// </summary> - /// <value></value> - /// <remarks> - /// This property is set by the channel when it is first constructed. - /// </remarks> - Channel IChannelBindingElement.Channel { - get { throw new NotImplementedException(); } - set { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets the protection commonly offered (if any) by this binding element. - /// </summary> - /// <value></value> - /// <remarks> - /// This value is used to assist in sorting binding elements in the channel stack. - /// </remarks> - MessageProtections IChannelBindingElement.Protection { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Prepares a message for sending based on the rules of this channel binding element. - /// </summary> - /// <param name="message">The message to prepare for sending.</param> - /// <returns> - /// The protections (if any) that this binding element applied to the message. - /// Null if this binding element did not even apply to this binding element. - /// </returns> - /// <remarks> - /// Implementations that provide message protection must honor the - /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. - /// </remarks> - MessageProtections? IChannelBindingElement.ProcessOutgoingMessage(IProtocolMessage message) { - Requires.NotNull(message, "message"); - Requires.ValidState(((IChannelBindingElement)this).Channel != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Performs any transformation on an incoming message that may be necessary and/or - /// validates an incoming message based on the rules of this channel binding element. - /// </summary> - /// <param name="message">The incoming message to process.</param> - /// <returns> - /// The protections (if any) that this binding element applied to the message. - /// Null if this binding element did not even apply to this binding element. - /// </returns> - /// <exception cref="ProtocolException"> - /// Thrown when the binding element rules indicate that this message is invalid and should - /// NOT be processed. - /// </exception> - /// <remarks> - /// Implementations that provide message protection must honor the - /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. - /// </remarks> - MessageProtections? IChannelBindingElement.ProcessIncomingMessage(IProtocolMessage message) { - Requires.NotNull(message, "message"); - Requires.ValidState(((IChannelBindingElement)this).Channel != null); - throw new NotImplementedException(); - } - - #endregion + Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken); } } diff --git a/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs b/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs index 0d1ab03..955d7c0 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs @@ -6,13 +6,12 @@ namespace DotNetOpenAuth.Messaging { using System; - using System.Diagnostics.Contracts; + using Validation; /// <summary> /// A serializer for <see cref="DataBag"/>-derived types /// </summary> /// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam> - [ContractClass(typeof(IDataBagFormatterContract<>))] internal interface IDataBagFormatter<in T> where T : DataBag { /// <summary> /// Serializes the specified message. @@ -30,50 +29,4 @@ namespace DotNetOpenAuth.Messaging { /// <param name="messagePartName">The name of the parameter whose value is to be deserialized. Used for error message generation, but may be <c>null</c>.</param> void Deserialize(T message, string data, IProtocolMessage containingMessage = null, string messagePartName = null); } - - /// <summary> - /// Contract class for the IDataBagFormatter interface. - /// </summary> - /// <typeparam name="T">The type of DataBag to serialize.</typeparam> - [ContractClassFor(typeof(IDataBagFormatter<>))] - internal abstract class IDataBagFormatterContract<T> : IDataBagFormatter<T> where T : DataBag, new() { - /// <summary> - /// Prevents a default instance of the <see cref="IDataBagFormatterContract<T>"/> class from being created. - /// </summary> - private IDataBagFormatterContract() { - } - - #region IDataBagFormatter<T> Members - - /// <summary> - /// Serializes the specified message. - /// </summary> - /// <param name="message">The message to serialize. Must not be null.</param> - /// <returns>A non-null, non-empty value.</returns> - string IDataBagFormatter<T>.Serialize(T message) { - Requires.NotNull(message, "message"); - Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); - - throw new System.NotImplementedException(); - } - - /// <summary> - /// Deserializes a <see cref="DataBag"/>. - /// </summary> - /// <param name="message">The instance to deserialize into</param> - /// <param name="data">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> - /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param> - /// <param name="messagePartName">Name of the message part whose value is to be deserialized. Used for exception messages.</param> - void IDataBagFormatter<T>.Deserialize(T message, string data, IProtocolMessage containingMessage, string messagePartName) { - Requires.NotNull(message, "message"); - Requires.NotNull(containingMessage, "containingMessage"); - Requires.NotNullOrEmpty(data, "data"); - Requires.NotNullOrEmpty(messagePartName, "messagePartName"); - Contract.Ensures(Contract.Result<T>() != null); - - throw new System.NotImplementedException(); - } - - #endregion - } }
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs deleted file mode 100644 index 7878405..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs +++ /dev/null @@ -1,223 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IDirectWebRequestHandler.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A contract for <see cref="HttpWebRequest"/> handling. - /// </summary> - /// <remarks> - /// Implementations of this interface must be thread safe. - /// </remarks> - [ContractClass(typeof(IDirectWebRequestHandlerContract))] - public interface IDirectWebRequestHandler { - /// <summary> - /// Determines whether this instance can support the specified options. - /// </summary> - /// <param name="options">The set of options that might be given in a subsequent web request.</param> - /// <returns> - /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. - /// </returns> - [Pure] - bool CanSupport(DirectWebRequestOptions options); - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <returns> - /// The stream the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method. - /// Callers <i>must</i> close and dispose of the request stream when they are done - /// writing to it to avoid taking up the connection too long and causing long waits on - /// subsequent requests.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - Stream GetRequestStream(HttpWebRequest request); - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// The stream the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method. - /// Callers <i>must</i> close and dispose of the request stream when they are done - /// writing to it to avoid taking up the connection too long and causing long waits on - /// subsequent requests.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options); - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - IncomingWebResponse GetResponse(HttpWebRequest request); - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options); - } - - /// <summary> - /// Code contract for the <see cref="IDirectWebRequestHandler"/> type. - /// </summary> - [ContractClassFor(typeof(IDirectWebRequestHandler))] - internal abstract class IDirectWebRequestHandlerContract : IDirectWebRequestHandler { - #region IDirectWebRequestHandler Members - - /// <summary> - /// Determines whether this instance can support the specified options. - /// </summary> - /// <param name="options">The set of options that might be given in a subsequent web request.</param> - /// <returns> - /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. - /// </returns> - bool IDirectWebRequestHandler.CanSupport(DirectWebRequestOptions options) { - throw new System.NotImplementedException(); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <returns> - /// The stream the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method. - /// Callers <i>must</i> close and dispose of the request stream when they are done - /// writing to it to avoid taking up the connection too long and causing long waits on - /// subsequent requests.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request) { - Requires.NotNull(request, "request"); - throw new System.NotImplementedException(); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// The stream the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method. - /// Callers <i>must</i> close and dispose of the request stream when they are done - /// writing to it to avoid taking up the connection too long and causing long waits on - /// subsequent requests.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { - Requires.NotNull(request, "request"); - Requires.Support(((IDirectWebRequestHandler)this).CanSupport(options), MessagingStrings.DirectWebRequestOptionsNotSupported); - ////ErrorUtilities.VerifySupported(((IDirectWebRequestHandler)this).CanSupport(options), string.Format(MessagingStrings.DirectWebRequestOptionsNotSupported, options, this.GetType().Name)); - throw new System.NotImplementedException(); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <returns> - /// An instance of <see cref="IncomingWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing. - /// </remarks> - IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) { - Requires.NotNull(request, "request"); - Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); - Contract.Ensures(Contract.Result<IncomingWebResponse>().ResponseStream != null); - throw new System.NotImplementedException(); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// An instance of <see cref="IncomingWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing. - /// </remarks> - IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { - Requires.NotNull(request, "request"); - Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); - Contract.Ensures(Contract.Result<IncomingWebResponse>().ResponseStream != null); - Requires.Support(((IDirectWebRequestHandler)this).CanSupport(options), MessagingStrings.DirectWebRequestOptionsNotSupported); - - ////ErrorUtilities.VerifySupported(((IDirectWebRequestHandler)this).CanSupport(options), string.Format(MessagingStrings.DirectWebRequestOptionsNotSupported, options, this.GetType().Name)); - throw new System.NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs index 7153334..226102d 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs @@ -5,18 +5,16 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging { - using System.Diagnostics.Contracts; using System.Net; /// <summary> /// An interface that allows direct request messages to capture the details of the HTTP request they arrived on. /// </summary> - [ContractClass(typeof(IHttpDirectRequestContract))] public interface IHttpDirectRequest : IMessage { /// <summary> /// Gets the HTTP headers of the request. /// </summary> /// <value>May be an empty collection, but must not be <c>null</c>.</value> - WebHeaderCollection Headers { get; } + System.Net.Http.Headers.HttpRequestHeaders Headers { get; } } } diff --git a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs deleted file mode 100644 index cfde6cf..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs +++ /dev/null @@ -1,75 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IHttpDirectRequestContract.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Net; - using System.Text; - - /// <summary> - /// Contract class for the <see cref="IHttpDirectRequest"/> interface. - /// </summary> - [ContractClassFor(typeof(IHttpDirectRequest))] - public abstract class IHttpDirectRequestContract : IHttpDirectRequest { - #region IHttpDirectRequest Members - - /// <summary> - /// Gets the HTTP headers of the request. - /// </summary> - /// <value>May be an empty collection, but must not be <c>null</c>.</value> - WebHeaderCollection IHttpDirectRequest.Headers { - get { - Contract.Ensures(Contract.Result<WebHeaderCollection>() != null); - throw new NotImplementedException(); - } - } - - #endregion - - #region IMessage Members - - /// <summary> - /// Gets the version of the protocol or extension this message is prepared to implement. - /// </summary> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - Version IMessage.Version { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets the extra, non-standard Protocol parameters included in the message. - /// </summary> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - IDictionary<string, string> IMessage.ExtraData { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - void IMessage.EnsureValidMessage() { - throw new NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponse.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponse.cs index d942366..f455fcf 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponse.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponse.cs @@ -5,14 +5,12 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging { - using System.Diagnostics.Contracts; using System.Net; /// <summary> /// An interface that allows direct response messages to specify /// HTTP transport specific properties. /// </summary> - [ContractClass(typeof(IHttpDirectResponseContract))] public interface IHttpDirectResponse { /// <summary> /// Gets the HTTP status code that the direct response should be sent with. diff --git a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponseContract.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponseContract.cs deleted file mode 100644 index a04ba62..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectResponseContract.cs +++ /dev/null @@ -1,43 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IHttpDirectResponseContract.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Net; - using System.Text; - - /// <summary> - /// Contract class for the <see cref="IHttpDirectResponse"/> interface. - /// </summary> - [ContractClassFor(typeof(IHttpDirectResponse))] - public abstract class IHttpDirectResponseContract : IHttpDirectResponse { - #region IHttpDirectResponse Members - - /// <summary> - /// Gets the HTTP status code that the direct response should be sent with. - /// </summary> - /// <value></value> - HttpStatusCode IHttpDirectResponse.HttpStatusCode { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets the HTTP headers to add to the response. - /// </summary> - /// <value>May be an empty collection, but must not be <c>null</c>.</value> - WebHeaderCollection IHttpDirectResponse.Headers { - get { - Contract.Ensures(Contract.Result<WebHeaderCollection>() != null); - throw new NotImplementedException(); - } - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/IHttpIndirectResponse.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpIndirectResponse.cs index e0e8665..c9ab73b 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IHttpIndirectResponse.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IHttpIndirectResponse.cs @@ -5,7 +5,6 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging { - using System.Diagnostics.Contracts; using System.Net; /// <summary> diff --git a/src/DotNetOpenAuth.Core/Messaging/IMessage.cs b/src/DotNetOpenAuth.Core/Messaging/IMessage.cs index 62673ef..c007913 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IMessage.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IMessage.cs @@ -7,14 +7,12 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; using System.Text; /// <summary> /// The interface that classes must implement to be serialized/deserialized /// as protocol or extension messages. /// </summary> - [ContractClass(typeof(IMessageContract))] public interface IMessage { /// <summary> /// Gets the version of the protocol or extension this message is prepared to implement. @@ -46,55 +44,4 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> void EnsureValidMessage(); } - - /// <summary> - /// Code contract for the <see cref="IMessage"/> interface. - /// </summary> - [ContractClassFor(typeof(IMessage))] - internal abstract class IMessageContract : IMessage { - /// <summary> - /// Prevents a default instance of the <see cref="IMessageContract"/> class from being created. - /// </summary> - private IMessageContract() { - } - - /// <summary> - /// Gets the version of the protocol or extension this message is prepared to implement. - /// </summary> - Version IMessage.Version { - get { - Contract.Ensures(Contract.Result<Version>() != null); - return default(Version); // dummy return - } - } - - /// <summary> - /// Gets the extra, non-standard Protocol parameters included in the message. - /// </summary> - /// <value></value> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - IDictionary<string, string> IMessage.ExtraData { - get { - Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); - return default(IDictionary<string, string>); - } - } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - void IMessage.EnsureValidMessage() { - } - } } diff --git a/src/DotNetOpenAuth.Core/Messaging/IMessageFactory.cs b/src/DotNetOpenAuth.Core/Messaging/IMessageFactory.cs index e45ac1d..1e86328 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IMessageFactory.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IMessageFactory.cs @@ -7,13 +7,12 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; + using Validation; /// <summary> /// A tool to analyze an incoming message to figure out what concrete class /// is designed to deserialize it and instantiates that class. /// </summary> - [ContractClass(typeof(IMessageFactoryContract))] public interface IMessageFactory { /// <summary> /// Analyzes an incoming request message payload to discover what kind of @@ -41,53 +40,4 @@ namespace DotNetOpenAuth.Messaging { /// </returns> IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields); } - - /// <summary> - /// Code contract for the <see cref="IMessageFactory"/> interface. - /// </summary> - [ContractClassFor(typeof(IMessageFactory))] - internal abstract class IMessageFactoryContract : IMessageFactory { - /// <summary> - /// Prevents a default instance of the <see cref="IMessageFactoryContract"/> class from being created. - /// </summary> - private IMessageFactoryContract() { - } - - #region IMessageFactory Members - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="recipient">The intended or actual recipient of the request message.</param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - IDirectedProtocolMessage IMessageFactory.GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { - Requires.NotNull(recipient, "recipient"); - Requires.NotNull(fields, "fields"); - - throw new NotImplementedException(); - } - - /// <summary> - /// Analyzes an incoming request message payload to discover what kind of - /// message is embedded in it and returns the type, or null if no match is found. - /// </summary> - /// <param name="request">The message that was sent as a request that resulted in the response.</param> - /// <param name="fields">The name/value pairs that make up the message payload.</param> - /// <returns> - /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can - /// deserialize to. Null if the request isn't recognized as a valid protocol message. - /// </returns> - IDirectResponseProtocolMessage IMessageFactory.GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { - Requires.NotNull(request, "request"); - Requires.NotNull(fields, "fields"); - throw new NotImplementedException(); - } - - #endregion - } } diff --git a/src/DotNetOpenAuth.Core/Messaging/IMessageOriginalPayload.cs b/src/DotNetOpenAuth.Core/Messaging/IMessageOriginalPayload.cs index 099f54b..33fa860 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IMessageOriginalPayload.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IMessageOriginalPayload.cs @@ -8,14 +8,12 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.Text; /// <summary> /// An interface that appears on messages that need to retain a description of /// what their literal payload was when they were deserialized. /// </summary> - [ContractClass(typeof(IMessageOriginalPayloadContract))] public interface IMessageOriginalPayload { /// <summary> /// Gets or sets the original message parts, before any normalization or default values were assigned. @@ -23,18 +21,4 @@ namespace DotNetOpenAuth.Messaging { [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "By design")] IDictionary<string, string> OriginalPayload { get; set; } } - - /// <summary> - /// Code contract for the <see cref="IMessageOriginalPayload"/> interface. - /// </summary> - [ContractClassFor(typeof(IMessageOriginalPayload))] - internal abstract class IMessageOriginalPayloadContract : IMessageOriginalPayload { - /// <summary> - /// Gets or sets the original message parts, before any normalization or default values were assigned. - /// </summary> - IDictionary<string, string> IMessageOriginalPayload.OriginalPayload { - get { throw new NotImplementedException(); } - set { throw new NotImplementedException(); } - } - } } diff --git a/src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs b/src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs index 60e1f50..84a7760 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs @@ -7,150 +7,24 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; using System.Linq; + using System.Net.Http; using System.Text; /// <summary> /// The interface that classes must implement to be serialized/deserialized /// as protocol or extension messages that uses POST multi-part data for binary content. /// </summary> - [ContractClass(typeof(IMessageWithBinaryDataContract))] public interface IMessageWithBinaryData : IDirectedProtocolMessage { /// <summary> /// Gets the parts of the message that carry binary data. /// </summary> /// <value>A list of parts. Never null.</value> - IList<MultipartPostPart> BinaryData { get; } + IList<MultipartContentMember> BinaryData { get; } /// <summary> /// Gets a value indicating whether this message should be sent as multi-part POST. /// </summary> bool SendAsMultipart { get; } } - - /// <summary> - /// The contract class for the <see cref="IMessageWithBinaryData"/> interface. - /// </summary> - [ContractClassFor(typeof(IMessageWithBinaryData))] - internal abstract class IMessageWithBinaryDataContract : IMessageWithBinaryData { - /// <summary> - /// Prevents a default instance of the <see cref="IMessageWithBinaryDataContract"/> class from being created. - /// </summary> - private IMessageWithBinaryDataContract() { - } - - #region IMessageWithBinaryData Members - - /// <summary> - /// Gets the parts of the message that carry binary data. - /// </summary> - /// <value>A list of parts. Never null.</value> - IList<MultipartPostPart> IMessageWithBinaryData.BinaryData { - get { - Contract.Ensures(Contract.Result<IList<MultipartPostPart>>() != null); - throw new NotImplementedException(); - } - } - - /// <summary> - /// Gets a value indicating whether this message should be sent as multi-part POST. - /// </summary> - bool IMessageWithBinaryData.SendAsMultipart { - get { throw new NotImplementedException(); } - } - - #endregion - - #region IMessage Properties - - /// <summary> - /// Gets the version of the protocol or extension this message is prepared to implement. - /// </summary> - /// <value></value> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - Version IMessage.Version { - get { - return default(Version); // dummy return - } - } - - /// <summary> - /// Gets the extra, non-standard Protocol parameters included in the message. - /// </summary> - /// <value></value> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - IDictionary<string, string> IMessage.ExtraData { - get { - return default(IDictionary<string, string>); - } - } - - #endregion - - #region IDirectedProtocolMessage Members - - /// <summary> - /// Gets the preferred method of transport for the message. - /// </summary> - /// <remarks> - /// For indirect messages this will likely be GET+POST, which both can be simulated in the user agent: - /// the GET with a simple 301 Redirect, and the POST with an HTML form in the response with javascript - /// to automate submission. - /// </remarks> - HttpDeliveryMethods IDirectedProtocolMessage.HttpMethods { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets the URL of the intended receiver of this message. - /// </summary> - Uri IDirectedProtocolMessage.Recipient { - get { throw new NotImplementedException(); } - } - - #endregion - - #region IProtocolMessage Members - - /// <summary> - /// Gets the level of protection this message requires. - /// </summary> - MessageProtections IProtocolMessage.RequiredProtection { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets a value indicating whether this is a direct or indirect message. - /// </summary> - MessageTransport IProtocolMessage.Transport { - get { throw new NotImplementedException(); } - } - - #endregion - - #region IMessage methods - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - void IMessage.EnsureValidMessage() { - throw new NotImplementedException(); - } - - #endregion - } } diff --git a/src/DotNetOpenAuth.Core/Messaging/IProtocolMessageWithExtensions.cs b/src/DotNetOpenAuth.Core/Messaging/IProtocolMessageWithExtensions.cs index c492e65..436c7a9 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IProtocolMessageWithExtensions.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IProtocolMessageWithExtensions.cs @@ -7,12 +7,10 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; /// <summary> /// A protocol message that supports adding extensions to the payload for transmission. /// </summary> - [ContractClass(typeof(IProtocolMessageWithExtensionsContract))] public interface IProtocolMessageWithExtensions : IProtocolMessage { /// <summary> /// Gets the list of extensions that are included with this message. @@ -22,95 +20,4 @@ namespace DotNetOpenAuth.Messaging { /// </remarks> IList<IExtensionMessage> Extensions { get; } } - - /// <summary> - /// Code contract for the <see cref="IProtocolMessageWithExtensions"/> interface. - /// </summary> - [ContractClassFor(typeof(IProtocolMessageWithExtensions))] - internal abstract class IProtocolMessageWithExtensionsContract : IProtocolMessageWithExtensions { - /// <summary> - /// Prevents a default instance of the <see cref="IProtocolMessageWithExtensionsContract"/> class from being created. - /// </summary> - private IProtocolMessageWithExtensionsContract() { - } - - #region IProtocolMessageWithExtensions Members - - /// <summary> - /// Gets the list of extensions that are included with this message. - /// </summary> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - IList<IExtensionMessage> IProtocolMessageWithExtensions.Extensions { - get { - Contract.Ensures(Contract.Result<IList<IExtensionMessage>>() != null); - throw new NotImplementedException(); - } - } - - #endregion - - #region IProtocolMessage Members - - /// <summary> - /// Gets the level of protection this message requires. - /// </summary> - MessageProtections IProtocolMessage.RequiredProtection { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Gets a value indicating whether this is a direct or indirect message. - /// </summary> - MessageTransport IProtocolMessage.Transport { - get { throw new NotImplementedException(); } - } - - #endregion - - #region IMessage Members - - /// <summary> - /// Gets the version of the protocol or extension this message is prepared to implement. - /// </summary> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - Version IMessage.Version { - get { - throw new NotImplementedException(); - } - } - - /// <summary> - /// Gets the extra, non-standard Protocol parameters included in the message. - /// </summary> - /// <remarks> - /// Implementations of this interface should ensure that this property never returns null. - /// </remarks> - IDictionary<string, string> IMessage.ExtraData { - get { - throw new NotImplementedException(); - } - } - - /// <summary> - /// Checks the message state for conformity to the protocol specification - /// and throws an exception if the message is invalid. - /// </summary> - /// <remarks> - /// <para>Some messages have required fields, or combinations of fields that must relate to each other - /// in specialized ways. After deserializing a message, this method checks the state of the - /// message to see if it conforms to the protocol.</para> - /// <para>Note that this property should <i>not</i> check signatures or perform any state checks - /// outside this scope of this particular message.</para> - /// </remarks> - /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> - void IMessage.EnsureValidMessage() { - throw new NotImplementedException(); - } - - #endregion - } } diff --git a/src/DotNetOpenAuth.Core/Messaging/IStreamSerializingDataBag.cs b/src/DotNetOpenAuth.Core/Messaging/IStreamSerializingDataBag.cs index cc82d6a..16fed67 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IStreamSerializingDataBag.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IStreamSerializingDataBag.cs @@ -6,13 +6,11 @@ namespace DotNetOpenAuth.Messaging { using System; - using System.Diagnostics.Contracts; using System.IO; /// <summary> /// An interface implemented by <see cref="DataBag"/>-derived types that support binary serialization. /// </summary> - [ContractClass(typeof(IStreamSerializingDataBaContract))] internal interface IStreamSerializingDataBag { /// <summary> /// Serializes the instance to the specified stream. @@ -26,30 +24,4 @@ namespace DotNetOpenAuth.Messaging { /// <param name="stream">The stream.</param> void Deserialize(Stream stream); } - - /// <summary> - /// Code Contract for the <see cref="IStreamSerializingDataBag"/> interface. - /// </summary> - [ContractClassFor(typeof(IStreamSerializingDataBag))] - internal abstract class IStreamSerializingDataBaContract : IStreamSerializingDataBag { - /// <summary> - /// Serializes the instance to the specified stream. - /// </summary> - /// <param name="stream">The stream.</param> - void IStreamSerializingDataBag.Serialize(Stream stream) { - Contract.Requires(stream != null); - Contract.Requires(stream.CanWrite); - throw new NotImplementedException(); - } - - /// <summary> - /// Initializes the fields on this instance from the specified stream. - /// </summary> - /// <param name="stream">The stream.</param> - void IStreamSerializingDataBag.Deserialize(Stream stream) { - Contract.Requires(stream != null); - Contract.Requires(stream.CanRead); - throw new NotImplementedException(); - } - } } diff --git a/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponse.cs deleted file mode 100644 index cdb26ae..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponse.cs +++ /dev/null @@ -1,191 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IncomingWebResponse.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Net; - using System.Net.Mime; - using System.Text; - - /// <summary> - /// Details on the incoming response from a direct web request to a remote party. - /// </summary> - [ContractVerification(true)] - [ContractClass(typeof(IncomingWebResponseContract))] - public abstract class IncomingWebResponse : IDisposable { - /// <summary> - /// The encoding to use in reading a response that does not declare its own content encoding. - /// </summary> - private const string DefaultContentEncoding = "ISO-8859-1"; - - /// <summary> - /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class. - /// </summary> - protected internal IncomingWebResponse() { - this.Status = HttpStatusCode.OK; - this.Headers = new WebHeaderCollection(); - } - - /// <summary> - /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class. - /// </summary> - /// <param name="requestUri">The original request URI.</param> - /// <param name="response">The response to initialize from. The network stream is used by this class directly.</param> - protected IncomingWebResponse(Uri requestUri, HttpWebResponse response) { - Requires.NotNull(requestUri, "requestUri"); - Requires.NotNull(response, "response"); - - this.RequestUri = requestUri; - if (!string.IsNullOrEmpty(response.ContentType)) { - try { - this.ContentType = new ContentType(response.ContentType); - } catch (FormatException) { - Logger.Messaging.ErrorFormat("HTTP response to {0} included an invalid Content-Type header value: {1}", response.ResponseUri.AbsoluteUri, response.ContentType); - } - } - this.ContentEncoding = string.IsNullOrEmpty(response.ContentEncoding) ? DefaultContentEncoding : response.ContentEncoding; - this.FinalUri = response.ResponseUri; - this.Status = response.StatusCode; - this.Headers = response.Headers; - } - - /// <summary> - /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class. - /// </summary> - /// <param name="requestUri">The request URI.</param> - /// <param name="responseUri">The final URI to respond to the request.</param> - /// <param name="headers">The headers.</param> - /// <param name="statusCode">The status code.</param> - /// <param name="contentType">Type of the content.</param> - /// <param name="contentEncoding">The content encoding.</param> - protected IncomingWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding) { - Requires.NotNull(requestUri, "requestUri"); - - this.RequestUri = requestUri; - this.Status = statusCode; - if (!string.IsNullOrEmpty(contentType)) { - try { - this.ContentType = new ContentType(contentType); - } catch (FormatException) { - Logger.Messaging.ErrorFormat("HTTP response to {0} included an invalid Content-Type header value: {1}", responseUri.AbsoluteUri, contentType); - } - } - this.ContentEncoding = string.IsNullOrEmpty(contentEncoding) ? DefaultContentEncoding : contentEncoding; - this.Headers = headers; - this.FinalUri = responseUri; - } - - /// <summary> - /// Gets the type of the content. - /// </summary> - public ContentType ContentType { get; private set; } - - /// <summary> - /// Gets the content encoding. - /// </summary> - public string ContentEncoding { get; private set; } - - /// <summary> - /// Gets the URI of the initial request. - /// </summary> - public Uri RequestUri { get; private set; } - - /// <summary> - /// Gets the URI that finally responded to the request. - /// </summary> - /// <remarks> - /// This can be different from the <see cref="RequestUri"/> in cases of - /// redirection during the request. - /// </remarks> - public Uri FinalUri { get; internal set; } - - /// <summary> - /// Gets the headers that must be included in the response to the user agent. - /// </summary> - /// <remarks> - /// The headers in this collection are not meant to be a comprehensive list - /// of exactly what should be sent, but are meant to augment whatever headers - /// are generally included in a typical response. - /// </remarks> - public WebHeaderCollection Headers { get; internal set; } - - /// <summary> - /// Gets the HTTP status code to use in the HTTP response. - /// </summary> - public HttpStatusCode Status { get; internal set; } - - /// <summary> - /// Gets the body of the HTTP response. - /// </summary> - public abstract Stream ResponseStream { get; } - - /// <summary> - /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </summary> - /// <returns> - /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </returns> - public override string ToString() { - StringBuilder sb = new StringBuilder(); - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "RequestUri = {0}", this.RequestUri)); - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ResponseUri = {0}", this.FinalUri)); - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "StatusCode = {0}", this.Status)); - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ContentType = {0}", this.ContentType)); - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ContentEncoding = {0}", this.ContentEncoding)); - sb.AppendLine("Headers:"); - foreach (string header in this.Headers) { - sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "\t{0}: {1}", header, this.Headers[header])); - } - - return sb.ToString(); - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns>The text reader, initialized for the proper encoding.</returns> - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly operation")] - public abstract StreamReader GetResponseReader(); - - /// <summary> - /// Gets an offline snapshot version of this instance. - /// </summary> - /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> - /// <returns>A snapshot version of this instance.</returns> - /// <remarks> - /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot - /// will automatically close and dispose of the underlying response stream. - /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will - /// be the self same instance. - /// </remarks> - internal abstract CachedDirectWebResponse GetSnapshot(int maximumBytesToCache); - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - Stream responseStream = this.ResponseStream; - if (responseStream != null) { - responseStream.Dispose(); - } - } - } - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponseContract.cs b/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponseContract.cs deleted file mode 100644 index 5c94e47..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponseContract.cs +++ /dev/null @@ -1,54 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IncomingWebResponseContract.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics.Contracts; - using System.IO; - - /// <summary> - /// Code contract for the <see cref="IncomingWebResponse"/> class. - /// </summary> - [ContractClassFor(typeof(IncomingWebResponse))] - internal abstract class IncomingWebResponseContract : IncomingWebResponse { - /// <summary> - /// Gets the body of the HTTP response. - /// </summary> - /// <value></value> - public override Stream ResponseStream { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns> - /// The text reader, initialized for the proper encoding. - /// </returns> - public override StreamReader GetResponseReader() { - Contract.Ensures(Contract.Result<StreamReader>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets an offline snapshot version of this instance. - /// </summary> - /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> - /// <returns>A snapshot version of this instance.</returns> - /// <remarks> - /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot - /// will automatically close and dispose of the underlying response stream. - /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will - /// be the self same instance. - /// </remarks> - internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { - Requires.InRange(maximumBytesToCache >= 0, "maximumBytesToCache"); - Requires.ValidState(this.RequestUri != null); - Contract.Ensures(Contract.Result<CachedDirectWebResponse>() != null); - throw new NotImplementedException(); - } - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/KeyedCollectionDelegate.cs b/src/DotNetOpenAuth.Core/Messaging/KeyedCollectionDelegate.cs index d0988c8..251ff30 100644 --- a/src/DotNetOpenAuth.Core/Messaging/KeyedCollectionDelegate.cs +++ b/src/DotNetOpenAuth.Core/Messaging/KeyedCollectionDelegate.cs @@ -7,7 +7,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.ObjectModel; - using System.Diagnostics.Contracts; + using Validation; /// <summary> /// A KeyedCollection whose item -> key transform is provided via a delegate diff --git a/src/DotNetOpenAuth.Core/Messaging/MessageProtectionTasks.cs b/src/DotNetOpenAuth.Core/Messaging/MessageProtectionTasks.cs new file mode 100644 index 0000000..37c86ca --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/MessageProtectionTasks.cs @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageProtectionTasks.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + /// <summary> + /// Reusable pre-completed tasks that may be returned multiple times to reduce GC pressure. + /// </summary> + internal static class MessageProtectionTasks { + /// <summary> + /// A task whose result is <c>null</c> + /// </summary> + internal static readonly Task<MessageProtections?> Null = Task.FromResult<MessageProtections?>(null); + + /// <summary> + /// A task whose result is <see cref="MessageProtections.None"/> + /// </summary> + internal static readonly Task<MessageProtections?> None = + Task.FromResult<MessageProtections?>(MessageProtections.None); + + /// <summary> + /// A task whose result is <see cref="MessageProtections.TamperProtection"/> + /// </summary> + internal static readonly Task<MessageProtections?> TamperProtection = + Task.FromResult<MessageProtections?>(MessageProtections.TamperProtection); + + /// <summary> + /// A task whose result is <see cref="MessageProtections.ReplayProtection"/> + /// </summary> + internal static readonly Task<MessageProtections?> ReplayProtection = + Task.FromResult<MessageProtections?>(MessageProtections.ReplayProtection); + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/MessageReceivingEndpoint.cs b/src/DotNetOpenAuth.Core/Messaging/MessageReceivingEndpoint.cs index cf5ea92..34be92d 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessageReceivingEndpoint.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessageReceivingEndpoint.cs @@ -7,7 +7,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Diagnostics; - using System.Diagnostics.Contracts; + using Validation; /// <summary> /// An immutable description of a URL that receives messages. @@ -23,8 +23,8 @@ namespace DotNetOpenAuth.Messaging { public MessageReceivingEndpoint(string locationUri, HttpDeliveryMethods method) : this(new Uri(locationUri), method) { Requires.NotNull(locationUri, "locationUri"); - Requires.InRange(method != HttpDeliveryMethods.None, "method"); - Requires.InRange((method & HttpDeliveryMethods.HttpVerbMask) != 0, "method", MessagingStrings.GetOrPostFlagsRequired); + Requires.Range(method != HttpDeliveryMethods.None, "method"); + Requires.Range((method & HttpDeliveryMethods.HttpVerbMask) != 0, "method", MessagingStrings.GetOrPostFlagsRequired); } /// <summary> @@ -34,8 +34,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="method">The HTTP method(s) allowed.</param> public MessageReceivingEndpoint(Uri location, HttpDeliveryMethods method) { Requires.NotNull(location, "location"); - Requires.InRange(method != HttpDeliveryMethods.None, "method"); - Requires.InRange((method & HttpDeliveryMethods.HttpVerbMask) != 0, "method", MessagingStrings.GetOrPostFlagsRequired); + Requires.Range(method != HttpDeliveryMethods.None, "method"); + Requires.Range((method & HttpDeliveryMethods.HttpVerbMask) != 0, "method", MessagingStrings.GetOrPostFlagsRequired); this.Location = location; this.AllowedMethods = method; diff --git a/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs index 7391867..1b30748 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs @@ -14,11 +14,11 @@ namespace DotNetOpenAuth.Messaging { using System.Reflection; using System.Xml; using DotNetOpenAuth.Messaging.Reflection; + using Validation; /// <summary> /// Serializes/deserializes OAuth messages for/from transit. /// </summary> - [ContractVerification(true)] internal class MessageSerializer { /// <summary> /// The specific <see cref="IMessage"/>-derived type @@ -31,10 +31,8 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="messageType">The specific <see cref="IMessage"/>-derived type /// that will be serialized and deserialized using this class.</param> - [ContractVerification(false)] // bugs/limitations in CC static analysis private MessageSerializer(Type messageType) { - Requires.NotNullSubtype<IMessage>(messageType, "messageType"); - Contract.Ensures(this.messageType != null); + RequiresEx.NotNullSubtype<IMessage>(messageType, "messageType"); this.messageType = messageType; } @@ -43,9 +41,8 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="messageType">The type of message that will be serialized/deserialized.</param> /// <returns>A message serializer for the given message type.</returns> - [ContractVerification(false)] // bugs/limitations in CC static analysis - internal static MessageSerializer Get(Type messageType) { - Requires.NotNullSubtype<IMessage>(messageType, "messageType"); + internal static MessageSerializer Get(Type messageType) { + RequiresEx.NotNullSubtype<IMessage>(messageType, "messageType"); return new MessageSerializer(messageType); } @@ -94,7 +91,7 @@ namespace DotNetOpenAuth.Messaging { string type = "string"; MessagePart partDescription; if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { - Contract.Assume(partDescription != null); + Assumes.True(partDescription != null); if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { include = true; Type formattingType = partDescription.PreferredFormattingType; @@ -150,7 +147,6 @@ namespace DotNetOpenAuth.Messaging { [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Parallel design with Deserialize method.")] internal IDictionary<string, string> Serialize(MessageDictionary messageDictionary) { Requires.NotNull(messageDictionary, "messageDictionary"); - Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); // Rather than hand back the whole message dictionary (which // includes keys with blank values), create a new dictionary @@ -160,7 +156,7 @@ namespace DotNetOpenAuth.Messaging { foreach (var pair in messageDictionary) { MessagePart partDescription; if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { - Contract.Assume(partDescription != null); + Assumes.True(partDescription != null); if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { result.Add(pair.Key, pair.Value); } diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs index 0bcde95..9885eb2 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs @@ -9,26 +9,26 @@ namespace DotNetOpenAuth.Messaging { using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; -#if CLR4 using System.Net.Http; -#endif + using System.Net.Http.Headers; using System.Net.Mime; using System.Runtime.Serialization.Json; using System.Security; using System.Security.Cryptography; using System.Text; using System.Threading; + using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using System.Xml; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.Messaging.Reflection; + using Validation; /// <summary> /// A grab-bag of utility methods useful for the channel stack of the protocol. @@ -88,6 +88,11 @@ namespace DotNetOpenAuth.Messaging { private const int SymmetricSecretHandleLength = 4; /// <summary> + /// A pre-completed task. + /// </summary> + private static readonly Task CompletedTaskField = Task.FromResult<object>(null); + + /// <summary> /// The default lifetime of a private secret. /// </summary> private static readonly TimeSpan SymmetricSecretKeyLifespan = Configuration.DotNetOpenAuthSection.Messaging.PrivateSecretMaximumAge; @@ -151,44 +156,18 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Gets a random number generator for use on the current thread only. + /// Gets a pre-completed task. /// </summary> - internal static Random NonCryptoRandomDataGenerator { - get { return ThreadSafeRandom.RandomNumberGenerator; } - } - - /// <summary> - /// Transforms an OutgoingWebResponse to an MVC-friendly ActionResult. - /// </summary> - /// <param name="response">The response to send to the user agent.</param> - /// <returns>The <see cref="ActionResult"/> instance to be returned by the Controller's action method.</returns> - public static ActionResult AsActionResult(this OutgoingWebResponse response) { - Requires.NotNull(response, "response"); - return new OutgoingWebResponseActionResult(response); + internal static Task CompletedTask { + get { return CompletedTaskField; } } -#if CLR4 /// <summary> - /// Transforms an OutgoingWebResponse to a Web API-friendly HttpResponseMessage. + /// Gets a random number generator for use on the current thread only. /// </summary> - /// <param name="outgoingResponse">The response to send to the user agent.</param> - /// <returns>The <see cref="HttpResponseMessage"/> instance to be returned by the Web API method.</returns> - public static HttpResponseMessage AsHttpResponseMessage(this OutgoingWebResponse outgoingResponse) { - HttpResponseMessage response = new HttpResponseMessage(outgoingResponse.Status); - if (outgoingResponse.ResponseStream != null) { - response.Content = new StreamContent(outgoingResponse.ResponseStream); - } - - var responseHeaders = outgoingResponse.Headers; - foreach (var header in responseHeaders.AllKeys) { - if (!response.Headers.TryAddWithoutValidation(header, responseHeaders[header])) { - response.Content.Headers.TryAddWithoutValidation(header, responseHeaders[header]); - } - } - - return response; + internal static Random NonCryptoRandomDataGenerator { + get { return ThreadSafeRandom.RandomNumberGenerator; } } -#endif /// <summary> /// Gets the original request URL, as seen from the browser before any URL rewrites on the server if any. @@ -198,7 +177,7 @@ namespace DotNetOpenAuth.Messaging { [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "The Uri merging requires use of a string value.")] [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call should not be a property.")] public static Uri GetRequestUrlFromContext() { - Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + RequiresEx.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); return new HttpRequestWrapper(HttpContext.Current.Request).GetPublicFacingUrl(); } @@ -227,22 +206,6 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Sends a multipart HTTP POST request (useful for posting files). - /// </summary> - /// <param name="request">The HTTP request.</param> - /// <param name="requestHandler">The request handler.</param> - /// <param name="parts">The parts to include in the POST entity.</param> - /// <returns>The HTTP response.</returns> - public static IncomingWebResponse PostMultipart(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultipartPostPart> parts) { - Requires.NotNull(request, "request"); - Requires.NotNull(requestHandler, "requestHandler"); - Requires.NotNull(parts, "parts"); - - PostMultipartNoGetResponse(request, requestHandler, parts); - return requestHandler.GetResponse(request); - } - - /// <summary> /// Assembles a message comprised of the message on a given exception and all inner exceptions. /// </summary> /// <param name="exception">The exception.</param> @@ -397,7 +360,15 @@ namespace DotNetOpenAuth.Messaging { // HttpRequest.Url gives us the internal URL in a cloud environment, // So we use a variable that (at least from what I can tell) gives us // the public URL: - if (serverVariables["HTTP_HOST"] != null) { + string httpHost; + try { + httpHost = serverVariables["HTTP_HOST"]; + } catch (NullReferenceException) { + // The VS dev web server can throw this. :( + httpHost = null; + } + + if (httpHost != null) { ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols."); string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ?? (string.Equals(serverVariables["HTTP_FRONT_END_HTTPS"], "on", StringComparison.OrdinalIgnoreCase) ? Uri.UriSchemeHttps : request.Url.Scheme); @@ -431,6 +402,115 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Gets the public facing URL for the given incoming HTTP request. + /// </summary> + /// <returns>The URI that the outside world used to create this request.</returns> + public static Uri GetPublicFacingUrl() { + ErrorUtilities.VerifyHttpContext(); + return GetPublicFacingUrl(new HttpRequestWrapper(HttpContext.Current.Request)); + } + + /// <summary> + /// Wraps a response message as an MVC <see cref="ActionResult"/> so it can be conveniently returned from an MVC controller's action method. + /// </summary> + /// <param name="response">The response message.</param> + /// <returns>An <see cref="ActionResult"/> instance.</returns> + public static ActionResult AsActionResult(this HttpResponseMessage response) { + Requires.NotNull(response, "response"); + return new HttpResponseMessageActionResult(response); + } + + /// <summary> + /// Wraps an instance of <see cref="HttpRequestBase"/> as an <see cref="HttpRequestMessage"/> instance. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>An instance of <see cref="HttpRequestMessage"/></returns> + public static HttpRequestMessage AsHttpRequestMessage(this HttpRequestBase request) { + Requires.NotNull(request, "request"); + + Uri publicFacingUrl = request.GetPublicFacingUrl(); + var httpRequest = new HttpRequestMessage(new HttpMethod(request.HttpMethod), publicFacingUrl); + + if (request.Form != null) { + // Avoid a request message that will try to read the request stream twice for already parsed data. + httpRequest.Content = new FormUrlEncodedContent(request.Form.AsKeyValuePairs()); + } else if (request.InputStream != null) { + httpRequest.Content = new StreamContent(request.InputStream); + } + + httpRequest.CopyHeadersFrom(request); + return httpRequest; + } + + /// <summary> + /// Sends a response message to the HTTP client. + /// </summary> + /// <param name="response">The response message.</param> + /// <param name="context">The HTTP context to send the response with.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> + public static async Task SendAsync(this HttpResponseMessage response, HttpContextBase context = null, CancellationToken cancellationToken = default(CancellationToken)) { + Requires.NotNull(response, "response"); + if (context == null) { + ErrorUtilities.VerifyHttpContext(); + context = new HttpContextWrapper(HttpContext.Current); + } + + var responseContext = context.Response; + responseContext.StatusCode = (int)response.StatusCode; + responseContext.StatusDescription = response.ReasonPhrase; + foreach (var header in response.Headers) { + foreach (var value in header.Value) { + responseContext.AddHeader(header.Key, value); + } + } + + if (response.Content != null) { + await response.Content.CopyToAsync(responseContext.OutputStream).ConfigureAwait(false); + } + } + + /// <summary> + /// Disposes a value if it is not null. + /// </summary> + /// <param name="disposable">The disposable value.</param> + internal static void DisposeIfNotNull(this IDisposable disposable) { + if (disposable != null) { + disposable.Dispose(); + } + } + + /// <summary> + /// Clones the specified <see cref="HttpRequestMessage"/> so it can be re-sent. + /// </summary> + /// <param name="original">The original message.</param> + /// <returns>The cloned message</returns> + /// <remarks> + /// This is useful when an HTTP request fails, and after a little tweaking should be resent. + /// Since <see cref="HttpRequestMessage"/> remembers it was already sent, it will not permit being + /// sent a second time. This method clones the message so its contents are identical but allows + /// re-sending. + /// </remarks> + internal static HttpRequestMessage Clone(this HttpRequestMessage original) { + Requires.NotNull(original, "original"); + + var clone = new HttpRequestMessage(original.Method, original.RequestUri); + clone.Content = original.Content; + foreach (var header in original.Headers) { + clone.Headers.Add(header.Key, header.Value); + } + + foreach (var property in original.Properties) { + clone.Properties[property.Key] = property.Value; + } + + clone.Version = original.Version; + return clone; + } + + /// <summary> /// Gets the URL to the root of a web site, which may include a virtual directory path. /// </summary> /// <returns>An absolute URI.</returns> @@ -457,11 +537,7 @@ namespace DotNetOpenAuth.Messaging { return new XmlReaderSettings { MaxCharactersFromEntities = 1024, XmlResolver = null, -#if CLR4 DtdProcessing = DtdProcessing.Prohibit, -#else - ProhibitDtd = true, -#endif }; } @@ -505,59 +581,6 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Sends a multipart HTTP POST request (useful for posting files) but doesn't call GetResponse on it. - /// </summary> - /// <param name="request">The HTTP request.</param> - /// <param name="requestHandler">The request handler.</param> - /// <param name="parts">The parts to include in the POST entity.</param> - internal static void PostMultipartNoGetResponse(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultipartPostPart> parts) { - Requires.NotNull(request, "request"); - Requires.NotNull(requestHandler, "requestHandler"); - Requires.NotNull(parts, "parts"); - - Reporting.RecordFeatureUse("MessagingUtilities.PostMultipart"); - parts = parts.CacheGeneratedResults(); - string boundary = Guid.NewGuid().ToString(); - string initialPartLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "--{0}\r\n", boundary); - string partLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}\r\n", boundary); - string finalTrailingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}--\r\n", boundary); - var contentType = new ContentType("multipart/form-data") { - Boundary = boundary, - CharSet = Channel.PostEntityEncoding.WebName, - }; - - request.Method = "POST"; - request.ContentType = contentType.ToString(); - long contentLength = parts.Sum(p => partLeadingBoundary.Length + p.Length) + finalTrailingBoundary.Length; - if (parts.Any()) { - contentLength -= 2; // the initial part leading boundary has no leading \r\n - } - request.ContentLength = contentLength; - - var requestStream = requestHandler.GetRequestStream(request); - try { - StreamWriter writer = new StreamWriter(requestStream, Channel.PostEntityEncoding); - bool firstPart = true; - foreach (var part in parts) { - writer.Write(firstPart ? initialPartLeadingBoundary : partLeadingBoundary); - firstPart = false; - part.Serialize(writer); - part.Dispose(); - } - - writer.Write(finalTrailingBoundary); - writer.Flush(); - } finally { - // We need to be sure to close the request stream... - // unless it is a MemoryStream, which is a clue that we're in - // a mock stream situation and closing it would preclude reading it later. - if (!(requestStream is MemoryStream)) { - requestStream.Dispose(); - } - } - } - - /// <summary> /// Assembles the content of the HTTP Authorization or WWW-Authenticate header. /// </summary> /// <param name="fields">The fields to include.</param> @@ -603,25 +626,15 @@ namespace DotNetOpenAuth.Messaging { /// <param name="scheme">The scheme. Must not be null or empty.</param> /// <param name="authorizationHeader">The authorization header. May be null or empty.</param> /// <returns>A sequence of key=value pairs discovered in the header. Never null, but may be empty.</returns> - internal static IEnumerable<KeyValuePair<string, string>> ParseAuthorizationHeader(string scheme, string authorizationHeader) { + internal static IEnumerable<KeyValuePair<string, string>> ParseAuthorizationHeader(string scheme, AuthenticationHeaderValue authorizationHeader) { Requires.NotNullOrEmpty(scheme, "scheme"); - Contract.Ensures(Contract.Result<IEnumerable<KeyValuePair<string, string>>>() != null); - - string prefix = scheme + " "; - if (authorizationHeader != null) { - // The authorization header may have multiple sections. Look for the appropriate one. - string[] authorizationSections = new string[] { authorizationHeader }; // what is the right delimiter, if any? - foreach (string authorization in authorizationSections) { - string trimmedAuth = authorization.Trim(); - if (trimmedAuth.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { // RFC 2617 says this is case INsensitive - string data = trimmedAuth.Substring(prefix.Length); - return from element in data.Split(CommaArray) - let parts = element.Trim().Split(EqualsArray, 2) - let key = Uri.UnescapeDataString(parts[0]) - let value = Uri.UnescapeDataString(parts[1].Trim(QuoteArray)) - select new KeyValuePair<string, string>(key, value); - } - } + + if (authorizationHeader != null && authorizationHeader.Scheme.Equals(scheme, StringComparison.OrdinalIgnoreCase)) { // RFC 2617 says this is case INsensitive + return from element in authorizationHeader.Parameter.Split(CommaArray) + let parts = element.Trim().Split(EqualsArray, 2) + let key = Uri.UnescapeDataString(parts[0]) + let value = Uri.UnescapeDataString(parts[1].Trim(QuoteArray)) + select new KeyValuePair<string, string>(key, value); } return Enumerable.Empty<KeyValuePair<string, string>>(); @@ -637,7 +650,6 @@ namespace DotNetOpenAuth.Messaging { internal static string CombineKeyHandleAndPayload(string handle, string payload) { Requires.NotNullOrEmpty(handle, "handle"); Requires.NotNullOrEmpty(payload, "payload"); - Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); return handle + "!" + payload; } @@ -714,8 +726,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="allowableCharacters">The allowable characters.</param> /// <returns>A random string.</returns> internal static string GetRandomString(int length, string allowableCharacters) { - Requires.InRange(length >= 0, "length"); - Requires.True(allowableCharacters != null && allowableCharacters.Length >= 2, "allowableCharacters"); + Requires.Range(length >= 0, "length"); + Requires.That(allowableCharacters != null && allowableCharacters.Length >= 2, "allowableCharacters", "At least two allowable characters required."); char[] randomString = new char[length]; var random = NonCryptoRandomDataGenerator; @@ -736,7 +748,6 @@ namespace DotNetOpenAuth.Messaging { internal static string ComputeHash(this HashAlgorithm algorithm, string value, Encoding encoding = null) { Requires.NotNull(algorithm, "algorithm"); Requires.NotNull(value, "value"); - Contract.Ensures(Contract.Result<string>() != null); encoding = encoding ?? Encoding.UTF8; byte[] bytesToHash = encoding.GetBytes(value); @@ -755,7 +766,6 @@ namespace DotNetOpenAuth.Messaging { internal static string ComputeHash(this HashAlgorithm algorithm, IDictionary<string, string> data, Encoding encoding = null) { Requires.NotNull(algorithm, "algorithm"); Requires.NotNull(data, "data"); - Contract.Ensures(Contract.Result<string>() != null); // Assemble the dictionary to sign, taking care to remove the signature itself // in order to accurately reproduce the original signature (which of course didn't include @@ -776,7 +786,6 @@ namespace DotNetOpenAuth.Messaging { internal static string ComputeHash(this HashAlgorithm algorithm, IEnumerable<KeyValuePair<string, string>> sortedData, Encoding encoding = null) { Requires.NotNull(algorithm, "algorithm"); Requires.NotNull(sortedData, "sortedData"); - Contract.Ensures(Contract.Result<string>() != null); return ComputeHash(algorithm, CreateQueryString(sortedData), encoding); } @@ -955,7 +964,7 @@ namespace DotNetOpenAuth.Messaging { internal static KeyValuePair<string, CryptoKey> GetCurrentKey(this ICryptoKeyStore cryptoKeyStore, string bucket, TimeSpan minimumRemainingLife, int keySize = 256) { Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); Requires.NotNullOrEmpty(bucket, "bucket"); - Requires.True(keySize % 8 == 0, "keySize"); + Requires.That(keySize % 8 == 0, "keySize", "Key size must be a multiple of 8."); var cryptoKeyPair = cryptoKeyStore.GetKeys(bucket).FirstOrDefault(pair => pair.Value.Key.Length == keySize / 8); if (cryptoKeyPair.Value == null || cryptoKeyPair.Value.ExpiresUtc < DateTime.UtcNow + minimumRemainingLife) { @@ -998,7 +1007,6 @@ namespace DotNetOpenAuth.Messaging { [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")] internal static byte[] Compress(byte[] buffer, CompressionMethod method = CompressionMethod.Deflate) { Requires.NotNull(buffer, "buffer"); - Contract.Ensures(Contract.Result<byte[]>() != null); using (var ms = new MemoryStream()) { Stream compressingStream = null; @@ -1011,7 +1019,7 @@ namespace DotNetOpenAuth.Messaging { compressingStream = new GZipStream(ms, CompressionMode.Compress, true); break; default: - Requires.InRange(false, "method"); + Requires.Range(false, "method"); break; } @@ -1035,7 +1043,6 @@ namespace DotNetOpenAuth.Messaging { [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] internal static byte[] Decompress(byte[] buffer, CompressionMethod method = CompressionMethod.Deflate) { Requires.NotNull(buffer, "buffer"); - Contract.Ensures(Contract.Result<byte[]>() != null); using (var compressedDataStream = new MemoryStream(buffer)) { using (var decompressedDataStream = new MemoryStream()) { @@ -1049,7 +1056,7 @@ namespace DotNetOpenAuth.Messaging { decompressingStream = new GZipStream(compressedDataStream, CompressionMode.Decompress, true); break; default: - Requires.InRange(false, "method"); + Requires.Range(false, "method"); break; } @@ -1089,7 +1096,6 @@ namespace DotNetOpenAuth.Messaging { /// <returns>A data buffer.</returns> internal static byte[] FromBase64WebSafeString(string base64WebSafe) { Requires.NotNullOrEmpty(base64WebSafe, "base64WebSafe"); - Contract.Ensures(Contract.Result<byte[]>() != null); // Restore the padding characters and original URL-unsafe characters. int missingPaddingCharacters; @@ -1139,51 +1145,6 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance, - /// taking care to set some headers to the appropriate properties of - /// <see cref="HttpResponse" /> - /// </summary> - /// <param name="headers">The headers to add.</param> - /// <param name="response">The <see cref="HttpListenerResponse"/> instance to set the appropriate values to.</param> - internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpListenerResponse response) { - Requires.NotNull(headers, "headers"); - Requires.NotNull(response, "response"); - - foreach (string headerName in headers) { - switch (headerName) { - case "Content-Type": - response.ContentType = headers[HttpResponseHeader.ContentType]; - break; - - // Add more special cases here as necessary. - default: - response.AddHeader(headerName, headers[headerName]); - break; - } - } - } - -#if !CLR4 - /// <summary> - /// Copies the contents of one stream to another. - /// </summary> - /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param> - /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param> - /// <returns>The total number of bytes copied.</returns> - /// <remarks> - /// Copying begins at the streams' current positions. - /// The positions are NOT reset after copying is complete. - /// </remarks> - internal static int CopyTo(this Stream copyFrom, Stream copyTo) { - Requires.NotNull(copyFrom, "copyFrom"); - Requires.NotNull(copyTo, "copyTo"); - Requires.True(copyFrom.CanRead, "copyFrom", MessagingStrings.StreamUnreadable); - Requires.True(copyTo.CanWrite, "copyTo", MessagingStrings.StreamUnwritable); - return CopyUpTo(copyFrom, copyTo, int.MaxValue); - } -#endif - - /// <summary> /// Copies the contents of one stream to another. /// </summary> /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param> @@ -1197,8 +1158,8 @@ namespace DotNetOpenAuth.Messaging { internal static int CopyUpTo(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy) { Requires.NotNull(copyFrom, "copyFrom"); Requires.NotNull(copyTo, "copyTo"); - Requires.True(copyFrom.CanRead, "copyFrom", MessagingStrings.StreamUnreadable); - Requires.True(copyTo.CanWrite, "copyTo", MessagingStrings.StreamUnwritable); + Requires.That(copyFrom.CanRead, "copyFrom", MessagingStrings.StreamUnreadable); + Requires.That(copyTo.CanWrite, "copyTo", MessagingStrings.StreamUnwritable); byte[] buffer = new byte[1024]; int readBytes; @@ -1220,7 +1181,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>A seekable stream with the same contents as the original.</returns> internal static Stream CreateSnapshot(this Stream copyFrom) { Requires.NotNull(copyFrom, "copyFrom"); - Requires.True(copyFrom.CanRead, "copyFrom", MessagingStrings.StreamUnreadable); + Requires.That(copyFrom.CanRead, "copyFrom", MessagingStrings.StreamUnreadable); MemoryStream copyTo = new MemoryStream(copyFrom.CanSeek ? (int)copyFrom.Length : 4 * 1024); try { @@ -1234,80 +1195,20 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Clones an <see cref="HttpWebRequest"/> in order to send it again. + /// Clones an <see cref="HttpWebRequest" /> in order to send it again. /// </summary> - /// <param name="request">The request to clone.</param> - /// <returns>The newly created instance.</returns> - internal static HttpWebRequest Clone(this HttpWebRequest request) { + /// <param name="message">The message to set headers on.</param> + /// <param name="request">The request with headers to clone.</param> + internal static void CopyHeadersFrom(this HttpRequestMessage message, HttpRequestBase request) { Requires.NotNull(request, "request"); - Requires.True(request.RequestUri != null, "request"); - return Clone(request, request.RequestUri); - } - - /// <summary> - /// Clones an <see cref="HttpWebRequest"/> in order to send it again. - /// </summary> - /// <param name="request">The request to clone.</param> - /// <param name="newRequestUri">The new recipient of the request.</param> - /// <returns>The newly created instance.</returns> - internal static HttpWebRequest Clone(this HttpWebRequest request, Uri newRequestUri) { - Requires.NotNull(request, "request"); - Requires.NotNull(newRequestUri, "newRequestUri"); - - var newRequest = (HttpWebRequest)WebRequest.Create(newRequestUri); + Requires.NotNull(message, "message"); - // First copy headers. Only set those that are explicitly set on the original request, - // because some properties (like IfModifiedSince) activate special behavior when set, - // even when set to their "original" values. foreach (string headerName in request.Headers) { - switch (headerName) { - case "Accept": newRequest.Accept = request.Accept; break; - case "Connection": break; // Keep-Alive controls this - case "Content-Length": newRequest.ContentLength = request.ContentLength; break; - case "Content-Type": newRequest.ContentType = request.ContentType; break; - case "Expect": newRequest.Expect = request.Expect; break; - case "Host": break; // implicitly copied as part of the RequestUri - case "If-Modified-Since": newRequest.IfModifiedSince = request.IfModifiedSince; break; - case "Keep-Alive": newRequest.KeepAlive = request.KeepAlive; break; - case "Proxy-Connection": break; // no property equivalent? - case "Referer": newRequest.Referer = request.Referer; break; - case "Transfer-Encoding": newRequest.TransferEncoding = request.TransferEncoding; break; - case "User-Agent": newRequest.UserAgent = request.UserAgent; break; - default: newRequest.Headers[headerName] = request.Headers[headerName]; break; + string[] headerValues = request.Headers.GetValues(headerName); + if (!message.Headers.TryAddWithoutValidation(headerName, headerValues)) { + message.Content.Headers.TryAddWithoutValidation(headerName, headerValues); } } - - newRequest.AllowAutoRedirect = request.AllowAutoRedirect; - newRequest.AllowWriteStreamBuffering = request.AllowWriteStreamBuffering; - newRequest.AuthenticationLevel = request.AuthenticationLevel; - newRequest.AutomaticDecompression = request.AutomaticDecompression; - newRequest.CachePolicy = request.CachePolicy; - newRequest.ClientCertificates = request.ClientCertificates; - newRequest.ConnectionGroupName = request.ConnectionGroupName; - newRequest.ContinueDelegate = request.ContinueDelegate; - newRequest.CookieContainer = request.CookieContainer; - newRequest.Credentials = request.Credentials; - newRequest.ImpersonationLevel = request.ImpersonationLevel; - newRequest.MaximumAutomaticRedirections = request.MaximumAutomaticRedirections; - newRequest.MaximumResponseHeadersLength = request.MaximumResponseHeadersLength; - newRequest.MediaType = request.MediaType; - newRequest.Method = request.Method; - newRequest.Pipelined = request.Pipelined; - newRequest.PreAuthenticate = request.PreAuthenticate; - newRequest.ProtocolVersion = request.ProtocolVersion; - newRequest.ReadWriteTimeout = request.ReadWriteTimeout; - newRequest.SendChunked = request.SendChunked; - newRequest.Timeout = request.Timeout; - newRequest.UseDefaultCredentials = request.UseDefaultCredentials; - - try { - newRequest.Proxy = request.Proxy; - newRequest.UnsafeAuthenticatedConnectionSharing = request.UnsafeAuthenticatedConnectionSharing; - } catch (SecurityException) { - Logger.Messaging.Warn("Unable to clone some HttpWebRequest properties due to partial trust."); - } - - return newRequest; } /// <summary> @@ -1453,7 +1354,6 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The formulated querystring style string.</returns> internal static string CreateQueryString(IEnumerable<KeyValuePair<string, string>> args) { Requires.NotNull(args, "args"); - Contract.Ensures(Contract.Result<string>() != null); if (!args.Any()) { return string.Empty; @@ -1559,8 +1459,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The request to get recipient information from.</param> /// <returns>The recipient.</returns> /// <exception cref="ArgumentException">Thrown if the HTTP request is something we can't handle.</exception> - internal static MessageReceivingEndpoint GetRecipient(this HttpRequestBase request) { - return new MessageReceivingEndpoint(request.GetPublicFacingUrl(), GetHttpDeliveryMethod(request.HttpMethod)); + internal static MessageReceivingEndpoint GetRecipient(this HttpRequestMessage request) { + return new MessageReceivingEndpoint(request.RequestUri, GetHttpDeliveryMethod(request.Method.Method)); } /// <summary> @@ -1594,23 +1494,23 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="httpMethod">The HTTP method.</param> /// <returns>An HTTP verb, such as GET, POST, PUT, DELETE, PATCH, or OPTION.</returns> - internal static string GetHttpVerb(HttpDeliveryMethods httpMethod) { + internal static HttpMethod GetHttpVerb(HttpDeliveryMethods httpMethod) { if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.GetRequest) { - return "GET"; + return HttpMethod.Get; } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.PostRequest) { - return "POST"; + return HttpMethod.Post; } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.PutRequest) { - return "PUT"; + return HttpMethod.Put; } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.DeleteRequest) { - return "DELETE"; + return HttpMethod.Delete; } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.HeadRequest) { - return "HEAD"; + return HttpMethod.Head; } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.PatchRequest) { - return "PATCH"; + return new HttpMethod("PATCH"); } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.OptionsRequest) { - return "OPTIONS"; + return HttpMethod.Options; } else if ((httpMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) { - return "GET"; // if AuthorizationHeaderRequest is specified without an explicit HTTP verb, assume GET. + return HttpMethod.Get; // if AuthorizationHeaderRequest is specified without an explicit HTTP verb, assume GET. } else { throw ErrorUtilities.ThrowArgumentNamed("httpMethod", MessagingStrings.UnsupportedHttpVerb, httpMethod); } @@ -1636,6 +1536,29 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Gets the URI that contains the entire payload that would be sent by the browser for the specified redirect-based request message. + /// </summary> + /// <param name="response">The redirecting response message.</param> + /// <returns>The absolute URI that could be retrieved to send the same message the browser would.</returns> + /// <exception cref="System.NotSupportedException">Thrown if the message is not a redirect message.</exception> + internal static Uri GetDirectUriRequest(this HttpResponseMessage response) { + Requires.NotNull(response, "response"); + Requires.Argument( + response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectKeepVerb + || response.StatusCode == HttpStatusCode.RedirectMethod || response.StatusCode == HttpStatusCode.TemporaryRedirect, + "response", + "Redirecting response expected."); + + if (response.Headers.Location != null) { + return response.Headers.Location; + } else { + // Some responses are so large that they're HTML/JS self-posting pages. + // We can't create long URLs for those, at present. + throw new NotSupportedException(); + } + } + + /// <summary> /// Collects a sequence of key=value pairs into a dictionary. /// </summary> /// <typeparam name="TKey">The type of the key.</typeparam> @@ -1672,7 +1595,6 @@ namespace DotNetOpenAuth.Messaging { /// <c>Dictionary<string, string></c> does not allow null keys. /// </remarks> internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc) { - Contract.Ensures((nvc != null && Contract.Result<Dictionary<string, string>>() != null) || (nvc == null && Contract.Result<Dictionary<string, string>>() == null)); return ToDictionary(nvc, false); } @@ -1688,7 +1610,6 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns> /// <exception cref="ArgumentException">Thrown if <paramref name="throwOnNullKey"/> is <c>true</c> and a null key is encountered.</exception> internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc, bool throwOnNullKey) { - Contract.Ensures((nvc != null && Contract.Result<Dictionary<string, string>>() != null) || (nvc == null && Contract.Result<Dictionary<string, string>>() == null)); if (nvc == null) { return null; } @@ -1742,7 +1663,6 @@ namespace DotNetOpenAuth.Messaging { Requires.NotNull(source, "source"); Requires.NotNull(comparer, "comparer"); Requires.NotNull(keySelector, "keySelector"); - Contract.Ensures(Contract.Result<IOrderedEnumerable<TSource>>() != null); return System.Linq.Enumerable.OrderBy<TSource, TKey>(source, keySelector, new ComparisonHelper<TKey>(comparer)); } @@ -1805,7 +1725,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The read buffer.</returns> internal static byte[] ReadBuffer(this BinaryReader reader, int maxBufferSize) { Requires.NotNull(reader, "reader"); - Requires.InRange(maxBufferSize > 0 && maxBufferSize < 1024 * 1024, "maxBufferSize"); + Requires.Range(maxBufferSize > 0 && maxBufferSize < 1024 * 1024, "maxBufferSize"); int length = reader.ReadInt32(); ErrorUtilities.VerifyProtocol(length <= maxBufferSize, MessagingStrings.DataCorruptionDetected); byte[] buffer = new byte[length]; @@ -1936,6 +1856,11 @@ namespace DotNetOpenAuth.Messaging { internal static string EscapeUriDataStringRfc3986(string value) { Requires.NotNull(value, "value"); + // fast path for empty values. + if (value.Length == 0) { + return value; + } + // Start with RFC 2396 escaping by calling the .NET method to do the work. // This MAY sometimes exhibit RFC 3986 behavior (according to the documentation). // If it does, the escaping we do that follows it will be a no-op since the @@ -2113,5 +2038,33 @@ namespace DotNetOpenAuth.Messaging { #endregion } + + /// <summary> + /// An MVC <see cref="ActionResult"/> that wraps an <see cref="HttpResponseMessage"/> + /// </summary> + private class HttpResponseMessageActionResult : ActionResult { + /// <summary> + /// The wrapped response. + /// </summary> + private readonly HttpResponseMessage response; + + /// <summary> + /// Initializes a new instance of the <see cref="HttpResponseMessageActionResult"/> class. + /// </summary> + /// <param name="response">The response.</param> + internal HttpResponseMessageActionResult(HttpResponseMessage response) { + Requires.NotNull(response, "response"); + this.response = response; + } + + /// <summary> + /// Enables processing of the result of an action method by a custom type that inherits from the <see cref="T:System.Web.Mvc.ActionResult" /> class. + /// </summary> + /// <param name="context">The context in which the result is executed. The context information includes the controller, HTTP content, request context, and route data.</param> + public override void ExecuteResult(ControllerContext context) { + // TODO: fix this to be asynchronous. + this.response.SendAsync(context.HttpContext).GetAwaiter().GetResult(); + } + } } } diff --git a/src/DotNetOpenAuth.Core/Messaging/MultipartContentMember.cs b/src/DotNetOpenAuth.Core/Messaging/MultipartContentMember.cs new file mode 100644 index 0000000..fd5bfb5 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/MultipartContentMember.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="MultipartContentMember.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Text; + using System.Threading.Tasks; + + /// <summary> + /// Describes a part from a multi-part POST. + /// </summary> + public struct MultipartContentMember { + /// <summary> + /// Initializes a new instance of the <see cref="MultipartContentMember"/> struct. + /// </summary> + /// <param name="content">The content.</param> + /// <param name="name">The name of this part as it may come from an HTML form.</param> + /// <param name="fileName">Name of the file.</param> + public MultipartContentMember(HttpContent content, string name = null, string fileName = null) + : this() { + this.Content = content; + this.Name = name; + this.FileName = fileName; + } + + /// <summary> + /// Gets or sets the content. + /// </summary> + /// <value> + /// The content. + /// </value> + public HttpContent Content { get; set; } + + /// <summary> + /// Gets or sets the HTML form name. + /// </summary> + /// <value> + /// The name. + /// </value> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the name of the file. + /// </summary> + /// <value> + /// The name of the file. + /// </value> + public string FileName { get; set; } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/MultipartPostPart.cs b/src/DotNetOpenAuth.Core/Messaging/MultipartPostPart.cs deleted file mode 100644 index 055e4b9..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/MultipartPostPart.cs +++ /dev/null @@ -1,223 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MultipartPostPart.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using System.Text; - - /// <summary> - /// Represents a single part in a HTTP multipart POST request. - /// </summary> - public class MultipartPostPart : IDisposable { - /// <summary> - /// The "Content-Disposition" string. - /// </summary> - private const string ContentDispositionHeader = "Content-Disposition"; - - /// <summary> - /// The two-character \r\n newline character sequence to use. - /// </summary> - private const string NewLine = "\r\n"; - - /// <summary> - /// Initializes a new instance of the <see cref="MultipartPostPart"/> class. - /// </summary> - /// <param name="contentDisposition">The content disposition of the part.</param> - public MultipartPostPart(string contentDisposition) { - Requires.NotNullOrEmpty(contentDisposition, "contentDisposition"); - - this.ContentDisposition = contentDisposition; - this.ContentAttributes = new Dictionary<string, string>(); - this.PartHeaders = new WebHeaderCollection(); - } - - /// <summary> - /// Gets or sets the content disposition. - /// </summary> - /// <value>The content disposition.</value> - public string ContentDisposition { get; set; } - - /// <summary> - /// Gets the key=value attributes that appear on the same line as the Content-Disposition. - /// </summary> - /// <value>The content attributes.</value> - public IDictionary<string, string> ContentAttributes { get; private set; } - - /// <summary> - /// Gets the headers that appear on subsequent lines after the Content-Disposition. - /// </summary> - public WebHeaderCollection PartHeaders { get; private set; } - - /// <summary> - /// Gets or sets the content of the part. - /// </summary> - public Stream Content { get; set; } - - /// <summary> - /// Gets the length of this entire part. - /// </summary> - /// <remarks>Useful for calculating the ContentLength HTTP header to send before actually serializing the content.</remarks> - public long Length { - get { - ErrorUtilities.VerifyOperation(this.Content != null && this.Content.Length >= 0, MessagingStrings.StreamMustHaveKnownLength); - - long length = 0; - length += ContentDispositionHeader.Length; - length += ": ".Length; - length += this.ContentDisposition.Length; - foreach (var pair in this.ContentAttributes) { - length += "; ".Length + pair.Key.Length + "=\"".Length + pair.Value.Length + "\"".Length; - } - - length += NewLine.Length; - foreach (string headerName in this.PartHeaders) { - length += headerName.Length; - length += ": ".Length; - length += this.PartHeaders[headerName].Length; - length += NewLine.Length; - } - - length += NewLine.Length; - length += this.Content.Length; - - return length; - } - } - - /// <summary> - /// Creates a part that represents a simple form field. - /// </summary> - /// <param name="name">The name of the form field.</param> - /// <param name="value">The value.</param> - /// <returns>The constructed part.</returns> - public static MultipartPostPart CreateFormPart(string name, string value) { - Requires.NotNullOrEmpty(name, "name"); - Requires.NotNull(value, "value"); - - var part = new MultipartPostPart("form-data"); - try { - part.ContentAttributes["name"] = name; - part.Content = new MemoryStream(Encoding.UTF8.GetBytes(value)); - return part; - } catch { - part.Dispose(); - throw; - } - } - - /// <summary> - /// Creates a part that represents a file attachment. - /// </summary> - /// <param name="name">The name of the form field.</param> - /// <param name="filePath">The path to the file to send.</param> - /// <param name="contentType">Type of the content in HTTP Content-Type format.</param> - /// <returns>The constructed part.</returns> - public static MultipartPostPart CreateFormFilePart(string name, string filePath, string contentType) { - Requires.NotNullOrEmpty(name, "name"); - Requires.NotNullOrEmpty(filePath, "filePath"); - Requires.NotNullOrEmpty(contentType, "contentType"); - - string fileName = Path.GetFileName(filePath); - var fileStream = File.OpenRead(filePath); - try { - return CreateFormFilePart(name, fileName, contentType, fileStream); - } catch { - fileStream.Dispose(); - throw; - } - } - - /// <summary> - /// Creates a part that represents a file attachment. - /// </summary> - /// <param name="name">The name of the form field.</param> - /// <param name="fileName">Name of the file as the server should see it.</param> - /// <param name="contentType">Type of the content in HTTP Content-Type format.</param> - /// <param name="content">The content of the file.</param> - /// <returns>The constructed part.</returns> - public static MultipartPostPart CreateFormFilePart(string name, string fileName, string contentType, Stream content) { - Requires.NotNullOrEmpty(name, "name"); - Requires.NotNullOrEmpty(fileName, "fileName"); - Requires.NotNullOrEmpty(contentType, "contentType"); - Requires.NotNull(content, "content"); - - var part = new MultipartPostPart("file"); - try { - part.ContentAttributes["name"] = name; - part.ContentAttributes["filename"] = fileName; - part.PartHeaders[HttpRequestHeader.ContentType] = contentType; - if (!contentType.StartsWith("text/", StringComparison.Ordinal)) { - part.PartHeaders["Content-Transfer-Encoding"] = "binary"; - } - - part.Content = content; - return part; - } catch { - part.Dispose(); - throw; - } - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Serializes the part to a stream. - /// </summary> - /// <param name="streamWriter">The stream writer.</param> - internal void Serialize(StreamWriter streamWriter) { - // VERY IMPORTANT: any changes at all made to this must be kept in sync with the - // Length property which calculates exactly how many bytes this method will write. - streamWriter.NewLine = NewLine; - streamWriter.Write("{0}: {1}", ContentDispositionHeader, this.ContentDisposition); - foreach (var pair in this.ContentAttributes) { - streamWriter.Write("; {0}=\"{1}\"", pair.Key, pair.Value); - } - - streamWriter.WriteLine(); - foreach (string headerName in this.PartHeaders) { - streamWriter.WriteLine("{0}: {1}", headerName, this.PartHeaders[headerName]); - } - - streamWriter.WriteLine(); - streamWriter.Flush(); - this.Content.CopyTo(streamWriter.BaseStream); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - this.Content.Dispose(); - } - } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void Invariant() { - Contract.Invariant(!string.IsNullOrEmpty(this.ContentDisposition)); - Contract.Invariant(this.PartHeaders != null); - Contract.Invariant(this.ContentAttributes != null); - } -#endif - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/NetworkDirectWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/NetworkDirectWebResponse.cs deleted file mode 100644 index 2c3ddac..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/NetworkDirectWebResponse.cs +++ /dev/null @@ -1,116 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="NetworkDirectWebResponse.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using System.Text; - - /// <summary> - /// A live network HTTP response - /// </summary> - [DebuggerDisplay("{Status} {ContentType.MediaType}")] - [ContractVerification(true)] - internal class NetworkDirectWebResponse : IncomingWebResponse, IDisposable { - /// <summary> - /// The network response object, used to initialize this instance, that still needs - /// to be closed if applicable. - /// </summary> - private HttpWebResponse httpWebResponse; - - /// <summary> - /// The incoming network response stream. - /// </summary> - private Stream responseStream; - - /// <summary> - /// A value indicating whether a stream reader has already been - /// created on this instance. - /// </summary> - private bool streamReadBegun; - - /// <summary> - /// Initializes a new instance of the <see cref="NetworkDirectWebResponse"/> class. - /// </summary> - /// <param name="requestUri">The request URI.</param> - /// <param name="response">The response.</param> - internal NetworkDirectWebResponse(Uri requestUri, HttpWebResponse response) - : base(requestUri, response) { - Requires.NotNull(requestUri, "requestUri"); - Requires.NotNull(response, "response"); - this.httpWebResponse = response; - this.responseStream = response.GetResponseStream(); - } - - /// <summary> - /// Gets the body of the HTTP response. - /// </summary> - public override Stream ResponseStream { - get { return this.responseStream; } - } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns>The text reader, initialized for the proper encoding.</returns> - public override StreamReader GetResponseReader() { - this.streamReadBegun = true; - if (this.responseStream == null) { - throw new ObjectDisposedException(GetType().Name); - } - - string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding]; - if (string.IsNullOrEmpty(contentEncoding)) { - return new StreamReader(this.ResponseStream); - } else { - return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding)); - } - } - - /// <summary> - /// Gets an offline snapshot version of this instance. - /// </summary> - /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> - /// <returns>A snapshot version of this instance.</returns> - /// <remarks> - /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot - /// will automatically close and dispose of the underlying response stream. - /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will - /// be the self same instance. - /// </remarks> - internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { - ErrorUtilities.VerifyOperation(!this.streamReadBegun, "Network stream reading has already begun."); - ErrorUtilities.VerifyOperation(this.httpWebResponse != null, "httpWebResponse != null"); - - this.streamReadBegun = true; - var result = new CachedDirectWebResponse(this.RequestUri, this.httpWebResponse, maximumBytesToCache); - this.Dispose(); - return result; - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected override void Dispose(bool disposing) { - if (disposing) { - if (this.responseStream != null) { - this.responseStream.Dispose(); - this.responseStream = null; - } - if (this.httpWebResponse != null) { - this.httpWebResponse.Close(); - this.httpWebResponse = null; - } - } - - base.Dispose(disposing); - } - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs deleted file mode 100644 index 3b9ab41..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs +++ /dev/null @@ -1,387 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OutgoingWebResponse.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Net; - using System.Net.Mime; - using System.ServiceModel.Web; - using System.Text; - using System.Threading; - using System.Web; - - /// <summary> - /// A protocol message (request or response) that passes from this - /// to a remote party via the user agent using a redirect or form - /// POST submission, OR a direct message response. - /// </summary> - /// <remarks> - /// <para>An instance of this type describes the HTTP response that must be sent - /// in response to the current HTTP request.</para> - /// <para>It is important that this response make up the entire HTTP response. - /// A hosting ASPX page should not be allowed to render its normal HTML output - /// after this response is sent. The normal rendered output of an ASPX page - /// can be canceled by calling <see cref="HttpResponse.End"/> after this message - /// is sent on the response stream.</para> - /// </remarks> - public class OutgoingWebResponse { - /// <summary> - /// The encoder to use for serializing the response body. - /// </summary> - private static Encoding bodyStringEncoder = new UTF8Encoding(false); - - /// <summary> - /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class. - /// </summary> - internal OutgoingWebResponse() { - this.Status = HttpStatusCode.OK; - this.Headers = new WebHeaderCollection(); - this.Cookies = new HttpCookieCollection(); - } - - /// <summary> - /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class - /// based on the contents of an <see cref="HttpWebResponse"/>. - /// </summary> - /// <param name="response">The <see cref="HttpWebResponse"/> to clone.</param> - /// <param name="maximumBytesToRead">The maximum bytes to read from the response stream.</param> - protected internal OutgoingWebResponse(HttpWebResponse response, int maximumBytesToRead) { - Requires.NotNull(response, "response"); - - this.Status = response.StatusCode; - this.Headers = response.Headers; - this.Cookies = new HttpCookieCollection(); - this.ResponseStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : (int)response.ContentLength); - using (Stream responseStream = response.GetResponseStream()) { - // BUGBUG: strictly speaking, is the response were exactly the limit, we'd report it as truncated here. - this.IsResponseTruncated = responseStream.CopyUpTo(this.ResponseStream, maximumBytesToRead) == maximumBytesToRead; - this.ResponseStream.Seek(0, SeekOrigin.Begin); - } - } - - /// <summary> - /// Gets the headers that must be included in the response to the user agent. - /// </summary> - /// <remarks> - /// The headers in this collection are not meant to be a comprehensive list - /// of exactly what should be sent, but are meant to augment whatever headers - /// are generally included in a typical response. - /// </remarks> - public WebHeaderCollection Headers { get; internal set; } - - /// <summary> - /// Gets the body of the HTTP response. - /// </summary> - public Stream ResponseStream { get; internal set; } - - /// <summary> - /// Gets a value indicating whether the response stream is incomplete due - /// to a length limitation imposed by the HttpWebRequest or calling method. - /// </summary> - public bool IsResponseTruncated { get; internal set; } - - /// <summary> - /// Gets the cookies collection to add as headers to the HTTP response. - /// </summary> - public HttpCookieCollection Cookies { get; internal set; } - - /// <summary> - /// Gets or sets the body of the response as a string. - /// </summary> - public string Body { - get { return this.ResponseStream != null ? this.GetResponseReader().ReadToEnd() : null; } - set { this.SetResponse(value, null); } - } - - /// <summary> - /// Gets the HTTP status code to use in the HTTP response. - /// </summary> - public HttpStatusCode Status { get; internal set; } - - /// <summary> - /// Gets or sets a reference to the actual protocol message that - /// is being sent via the user agent. - /// </summary> - internal IProtocolMessage OriginalMessage { get; set; } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns>The text reader, initialized for the proper encoding.</returns> - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly operation")] - public StreamReader GetResponseReader() { - this.ResponseStream.Seek(0, SeekOrigin.Begin); - string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding]; - if (string.IsNullOrEmpty(contentEncoding)) { - return new StreamReader(this.ResponseStream); - } else { - return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding)); - } - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and ends execution on the current page or handler. - /// </summary> - /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> - /// <remarks> - /// Requires a current HttpContext. - /// </remarks> - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual void Send() { - Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - - this.Send(HttpContext.Current); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and ends execution on the current page or handler. - /// </summary> - /// <param name="context">The context of the HTTP request whose response should be set. - /// Typically this is <see cref="HttpContext.Current"/>.</param> - /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual void Send(HttpContext context) { - this.Respond(new HttpContextWrapper(context), true); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and ends execution on the current page or handler. - /// </summary> - /// <param name="context">The context of the HTTP request whose response should be set. - /// Typically this is <see cref="HttpContext.Current"/>.</param> - /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual void Send(HttpContextBase context) { - this.Respond(context, true); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and signals ASP.NET to short-circuit the page execution pipeline - /// now that the response has been completed. - /// Not safe to call from ASP.NET web forms. - /// </summary> - /// <remarks> - /// Requires a current HttpContext. - /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because - /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. - /// Use the <see cref="Send()"/> method instead for web forms. - /// </remarks> - public virtual void Respond() { - Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - - this.Respond(HttpContext.Current); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and signals ASP.NET to short-circuit the page execution pipeline - /// now that the response has been completed. - /// Not safe to call from ASP.NET web forms. - /// </summary> - /// <param name="context">The context of the HTTP request whose response should be set. - /// Typically this is <see cref="HttpContext.Current"/>.</param> - /// <remarks> - /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because - /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. - /// Use the <see cref="Send()"/> method instead for web forms. - /// </remarks> - public void Respond(HttpContext context) { - Requires.NotNull(context, "context"); - this.Respond(new HttpContextWrapper(context)); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and signals ASP.NET to short-circuit the page execution pipeline - /// now that the response has been completed. - /// Not safe to call from ASP.NET web forms. - /// </summary> - /// <param name="context">The context of the HTTP request whose response should be set. - /// Typically this is <see cref="HttpContext.Current"/>.</param> - /// <remarks> - /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because - /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. - /// Use the <see cref="Send()"/> method instead for web forms. - /// </remarks> - public virtual void Respond(HttpContextBase context) { - Requires.NotNull(context, "context"); - - this.Respond(context, false); - } - - /// <summary> - /// Submits this response to a WCF response context. Only available when no response body is included. - /// </summary> - /// <param name="responseContext">The response context to apply the response to.</param> - public virtual void Respond(OutgoingWebResponseContext responseContext) { - Requires.NotNull(responseContext, "responseContext"); - if (this.ResponseStream != null) { - throw new NotSupportedException(Strings.ResponseBodyNotSupported); - } - - responseContext.StatusCode = this.Status; - responseContext.SuppressEntityBody = true; - foreach (string header in this.Headers) { - responseContext.Headers[header] = this.Headers[header]; - } - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent. - /// </summary> - /// <param name="response">The response to set to this message.</param> - public virtual void Send(HttpListenerResponse response) { - Requires.NotNull(response, "response"); - - response.StatusCode = (int)this.Status; - MessagingUtilities.ApplyHeadersToResponse(this.Headers, response); - foreach (HttpCookie httpCookie in this.Cookies) { - var cookie = new Cookie(httpCookie.Name, httpCookie.Value) { - Expires = httpCookie.Expires, - Path = httpCookie.Path, - HttpOnly = httpCookie.HttpOnly, - Secure = httpCookie.Secure, - Domain = httpCookie.Domain, - }; - response.AppendCookie(cookie); - } - - if (this.ResponseStream != null) { - response.ContentLength64 = this.ResponseStream.Length; - this.ResponseStream.CopyTo(response.OutputStream); - } - - response.OutputStream.Close(); - } - - /// <summary> - /// Gets the URI that, when requested with an HTTP GET request, - /// would transmit the message that normally would be transmitted via a user agent redirect. - /// </summary> - /// <param name="channel">The channel to use for encoding.</param> - /// <returns> - /// The URL that would transmit the original message. This URL may exceed the normal 2K limit, - /// and should therefore be broken up manually and POSTed as form fields when it exceeds this length. - /// </returns> - /// <remarks> - /// This is useful for desktop applications that will spawn a user agent to transmit the message - /// rather than cause a redirect. - /// </remarks> - internal Uri GetDirectUriRequest(Channel channel) { - Requires.NotNull(channel, "channel"); - - var message = this.OriginalMessage as IDirectedProtocolMessage; - if (message == null) { - throw new InvalidOperationException(); // this only makes sense for directed messages (indirect responses) - } - - var fields = channel.MessageDescriptions.GetAccessor(message).Serialize(); - UriBuilder builder = new UriBuilder(message.Recipient); - MessagingUtilities.AppendQueryArgs(builder, fields); - return builder.Uri; - } - - /// <summary> - /// Sets the response to some string, encoded as UTF-8. - /// </summary> - /// <param name="body">The string to set the response to.</param> - /// <param name="contentType">Type of the content. May be null.</param> - internal void SetResponse(string body, ContentType contentType) { - if (body == null) { - this.ResponseStream = null; - return; - } - - if (contentType == null) { - contentType = new ContentType("text/html"); - contentType.CharSet = bodyStringEncoder.WebName; - } else if (contentType.CharSet != bodyStringEncoder.WebName) { - // clone the original so we're not tampering with our inputs if it came as a parameter. - contentType = new ContentType(contentType.ToString()); - contentType.CharSet = bodyStringEncoder.WebName; - } - - this.Headers[HttpResponseHeader.ContentType] = contentType.ToString(); - this.ResponseStream = new MemoryStream(); - StreamWriter writer = new StreamWriter(this.ResponseStream, bodyStringEncoder); - writer.Write(body); - writer.Flush(); - this.ResponseStream.Seek(0, SeekOrigin.Begin); - this.Headers[HttpResponseHeader.ContentLength] = this.ResponseStream.Length.ToString(CultureInfo.InvariantCulture); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and signals ASP.NET to short-circuit the page execution pipeline - /// now that the response has been completed. - /// </summary> - /// <param name="context">The context of the HTTP request whose response should be set. - /// Typically this is <see cref="HttpContext.Current"/>.</param> - /// <param name="endRequest">If set to <c>false</c>, this method calls - /// <see cref="HttpApplication.CompleteRequest"/> rather than <see cref="HttpResponse.End"/> - /// to avoid a <see cref="ThreadAbortException"/>.</param> - protected internal void Respond(HttpContext context, bool endRequest) { - this.Respond(new HttpContextWrapper(context), endRequest); - } - - /// <summary> - /// Automatically sends the appropriate response to the user agent - /// and signals ASP.NET to short-circuit the page execution pipeline - /// now that the response has been completed. - /// </summary> - /// <param name="context">The context of the HTTP request whose response should be set. - /// Typically this is <see cref="HttpContext.Current"/>.</param> - /// <param name="endRequest">If set to <c>false</c>, this method calls - /// <see cref="HttpApplication.CompleteRequest"/> rather than <see cref="HttpResponse.End"/> - /// to avoid a <see cref="ThreadAbortException"/>.</param> - protected internal virtual void Respond(HttpContextBase context, bool endRequest) { - Requires.NotNull(context, "context"); - - context.Response.Clear(); - context.Response.StatusCode = (int)this.Status; - MessagingUtilities.ApplyHeadersToResponse(this.Headers, context.Response); - if (this.ResponseStream != null) { - try { - this.ResponseStream.CopyTo(context.Response.OutputStream); - } catch (HttpException ex) { - if (ex.ErrorCode == -2147467259 && context.Response.Output != null) { - // Test scenarios can generate this, since the stream is being spoofed: - // System.Web.HttpException: OutputStream is not available when a custom TextWriter is used. - context.Response.Output.Write(this.Body); - } else { - throw; - } - } - } - - foreach (string cookieName in this.Cookies) { - var cookie = this.Cookies[cookieName]; - context.Response.AppendCookie(cookie); - } - - if (endRequest) { - // This approach throws an exception in order that - // no more code is executed in the calling page. - // Microsoft no longer recommends this approach. - context.Response.End(); - } else if (context.ApplicationInstance != null) { - // This approach doesn't throw an exception, but - // still tells ASP.NET to short-circuit most of the - // request handling pipeline to speed things up. - context.ApplicationInstance.CompleteRequest(); - } - } - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs deleted file mode 100644 index 7691cc4..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs +++ /dev/null @@ -1,45 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OutgoingWebResponseActionResult.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics.Contracts; - using System.Web.Mvc; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// An ASP.NET MVC structure to represent the response to send - /// to the user agent when the controller has finished its work. - /// </summary> - internal class OutgoingWebResponseActionResult : ActionResult { - /// <summary> - /// The outgoing web response to send when the ActionResult is executed. - /// </summary> - private readonly OutgoingWebResponse response; - - /// <summary> - /// Initializes a new instance of the <see cref="OutgoingWebResponseActionResult"/> class. - /// </summary> - /// <param name="response">The response.</param> - internal OutgoingWebResponseActionResult(OutgoingWebResponse response) { - Requires.NotNull(response, "response"); - this.response = response; - } - - /// <summary> - /// Enables processing of the result of an action method by a custom type that inherits from <see cref="T:System.Web.Mvc.ActionResult"/>. - /// </summary> - /// <param name="context">The context in which to set the response.</param> - public override void ExecuteResult(ControllerContext context) { - this.response.Respond(context.HttpContext); - - // MVC likes to muck with our response. For example, when returning contrived 401 Unauthorized responses - // MVC will rewrite our response and turn it into a redirect, which breaks OAuth 2 authorization server token endpoints. - // It turns out we can prevent this unwanted behavior by flushing the response before returning from this method. - context.HttpContext.Response.Flush(); - } - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs b/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs index 982e1c0..a45e397 100644 --- a/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs +++ b/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs @@ -7,7 +7,6 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; using System.Security; using System.Security.Permissions; @@ -81,11 +80,7 @@ namespace DotNetOpenAuth.Messaging { /// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*"/> /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter"/> /// </PermissionSet> -#if CLR4 [SecurityCritical] -#else - [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] -#endif public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { base.GetObjectData(info, context); throw new NotImplementedException(); diff --git a/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs b/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs index c2dc34e..b5cab3b 100644 --- a/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs +++ b/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs @@ -8,7 +8,11 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; using System.Linq; + using System.Net.Http; using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Validation; /// <summary> /// An exception to represent errors in the local or remote implementation of the protocol @@ -60,10 +64,12 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Creates the HTTP response to forward to the client to report the error. /// </summary> - /// <returns>The HTTP response.</returns> - public OutgoingWebResponse CreateErrorResponse() { - var response = this.channel.PrepareResponse(this.ErrorResponseMessage); - return response; + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The HTTP response. + /// </returns> + public Task<HttpResponseMessage> CreateErrorResponseAsync(CancellationToken cancellationToken) { + return this.channel.PrepareResponseAsync(this.ErrorResponseMessage, cancellationToken); } } } diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/DefaultEncoderAttribute.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/DefaultEncoderAttribute.cs index d827972..adf0f33 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/DefaultEncoderAttribute.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/DefaultEncoderAttribute.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System.Collections.Generic; using System.Linq; using System.Text; + using Validation; /// <summary> /// Allows a custom class or struct to be serializable between itself and a string representation. @@ -21,7 +22,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="converterType">The <see cref="IMessagePartEncoder"/> implementing type to use for serializing this type.</param> public DefaultEncoderAttribute(Type converterType) { Requires.NotNull(converterType, "converterType"); - Requires.True(typeof(IMessagePartEncoder).IsAssignableFrom(converterType), "Argument must be a type that implements {0}.", typeof(IMessagePartEncoder).Name); + Requires.That(typeof(IMessagePartEncoder).IsAssignableFrom(converterType), "Argument must be a type that implements {0}.", typeof(IMessagePartEncoder).Name); this.Encoder = (IMessagePartEncoder)Activator.CreateInstance(converterType); } diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartEncoder.cs index 6186cd7..017c7d7 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartEncoder.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartEncoder.cs @@ -7,9 +7,9 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; using System.Linq; using System.Text; + using Validation; /// <summary> /// An interface describing how various objects can be serialized and deserialized between their object and string forms. @@ -17,7 +17,6 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <remarks> /// Implementations of this interface must include a default constructor and must be thread-safe. /// </remarks> - [ContractClass(typeof(IMessagePartEncoderContract))] public interface IMessagePartEncoder { /// <summary> /// Encodes the specified value. @@ -34,45 +33,4 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> object Decode(string value); } - - /// <summary> - /// Code contract for the <see cref="IMessagePartEncoder"/> type. - /// </summary> - [ContractClassFor(typeof(IMessagePartEncoder))] - internal abstract class IMessagePartEncoderContract : IMessagePartEncoder { - /// <summary> - /// Initializes a new instance of the <see cref="IMessagePartEncoderContract"/> class. - /// </summary> - protected IMessagePartEncoderContract() { - } - - #region IMessagePartEncoder Members - - /// <summary> - /// Encodes the specified value. - /// </summary> - /// <param name="value">The value. Guaranteed to never be null.</param> - /// <returns> - /// The <paramref name="value"/> in string form, ready for message transport. - /// </returns> - string IMessagePartEncoder.Encode(object value) { - Requires.NotNull(value, "value"); - throw new NotImplementedException(); - } - - /// <summary> - /// Decodes the specified value. - /// </summary> - /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> - /// <returns> - /// The deserialized form of the given string. - /// </returns> - /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> - object IMessagePartEncoder.Decode(string value) { - Requires.NotNull(value, "value"); - throw new NotImplementedException(); - } - - #endregion - } } diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescription.cs index 7e67842..cd04e1d 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescription.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescription.cs @@ -12,6 +12,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System.Globalization; using System.Linq; using System.Reflection; + using Validation; /// <summary> /// A mapping between serialized key names and <see cref="MessagePart"/> instances describing @@ -30,7 +31,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="messageType">Type of the message.</param> /// <param name="messageVersion">The message version.</param> internal MessageDescription(Type messageType, Version messageVersion) { - Requires.NotNullSubtype<IMessage>(messageType, "messageType"); + RequiresEx.NotNullSubtype<IMessage>(messageType, "messageType"); Requires.NotNull(messageVersion, "messageVersion"); this.MessageType = messageType; @@ -80,7 +81,6 @@ namespace DotNetOpenAuth.Messaging.Reflection { [Pure] internal MessageDictionary GetDictionary(IMessage message) { Requires.NotNull(message, "message"); - Contract.Ensures(Contract.Result<MessageDictionary>() != null); return this.GetDictionary(message, false); } @@ -93,7 +93,6 @@ namespace DotNetOpenAuth.Messaging.Reflection { [Pure] internal MessageDictionary GetDictionary(IMessage message, bool getOriginalValues) { Requires.NotNull(message, "message"); - Contract.Ensures(Contract.Result<MessageDictionary>() != null); return new MessageDictionary(message, this, getOriginalValues); } diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescriptionCollection.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescriptionCollection.cs index 3517abc..f27a7af 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescriptionCollection.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDescriptionCollection.cs @@ -10,11 +10,11 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; + using Validation; /// <summary> /// A cache of <see cref="MessageDescription"/> instances. /// </summary> - [ContractVerification(true)] internal class MessageDescriptionCollection : IEnumerable<MessageDescription> { /// <summary> /// A dictionary of reflected message types and the generated reflection information. @@ -68,9 +68,8 @@ namespace DotNetOpenAuth.Messaging.Reflection { [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "No localization required.")] [Pure] internal MessageDescription Get(Type messageType, Version messageVersion) { - Requires.NotNullSubtype<IMessage>(messageType, "messageType"); + RequiresEx.NotNullSubtype<IMessage>(messageType, "messageType"); Requires.NotNull(messageVersion, "messageVersion"); - Contract.Ensures(Contract.Result<MessageDescription>() != null); MessageTypeAndVersion key = new MessageTypeAndVersion(messageType, messageVersion); @@ -106,7 +105,6 @@ namespace DotNetOpenAuth.Messaging.Reflection { [Pure] internal MessageDescription Get(IMessage message) { Requires.NotNull(message, "message"); - Contract.Ensures(Contract.Result<MessageDescription>() != null); return this.Get(message.GetType(), message.Version); } @@ -136,8 +134,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <summary> /// A struct used as the key to bundle message type and version. /// </summary> - [ContractVerification(true)] - private struct MessageTypeAndVersion { + private struct MessageTypeAndVersion { /// <summary> /// Backing store for the <see cref="Type"/> property. /// </summary> diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDictionary.cs index cf44863..a2dddb2 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDictionary.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessageDictionary.cs @@ -11,13 +11,13 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; + using Validation; /// <summary> /// Wraps an <see cref="IMessage"/> instance in a dictionary that /// provides access to both well-defined message properties and "extra" /// name/value pairs that have no properties associated with them. /// </summary> - [ContractVerification(false)] internal class MessageDictionary : IDictionary<string, string> { /// <summary> /// The <see cref="IMessage"/> instance manipulated by this dictionary. @@ -55,7 +55,6 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> public IMessage Message { get { - Contract.Ensures(Contract.Result<IMessage>() != null); return this.message; } } @@ -65,7 +64,6 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> public MessageDescription Description { get { - Contract.Ensures(Contract.Result<MessageDescription>() != null); return this.description; } } @@ -380,7 +378,6 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <returns>The generated dictionary.</returns> [Pure] public IDictionary<string, string> Serialize() { - Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); return this.Serializer.Serialize(this); } diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs index a6e8da2..add4beb 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs @@ -9,18 +9,17 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Net.Security; using System.Reflection; using System.Xml; using DotNetOpenAuth.Configuration; + using Validation; /// <summary> /// Describes an individual member of a message and assists in its serialization. /// </summary> - [ContractVerification(true)] [DebuggerDisplay("MessagePart {Name}")] internal class MessagePart { /// <summary> @@ -66,20 +65,20 @@ namespace DotNetOpenAuth.Messaging.Reflection { [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Much more efficient initialization when we can call methods.")] static MessagePart() { Func<string, Uri> safeUri = str => { - Contract.Assume(str != null); + Assumes.True(str != null); return new Uri(str); }; Func<string, bool> safeBool = str => { - Contract.Assume(str != null); + Assumes.True(str != null); return bool.Parse(str); }; Func<byte[], string> safeFromByteArray = bytes => { - Contract.Assume(bytes != null); + Assumes.True(bytes != null); return Convert.ToBase64String(bytes); }; Func<string, byte[]> safeToByteArray = str => { - Contract.Assume(str != null); + Assumes.True(str != null); return Convert.FromBase64String(str); }; Map<Uri>(uri => uri.AbsoluteUri, uri => uri.OriginalString, safeUri); @@ -106,7 +105,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Unavoidable"), SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code contracts requires it.")] internal MessagePart(MemberInfo member, MessagePartAttribute attribute) { Requires.NotNull(member, "member"); - Requires.True(member is FieldInfo || member is PropertyInfo, "member"); + Requires.That(member is FieldInfo || member is PropertyInfo, "member", "Member must be a property or field."); Requires.NotNull(attribute, "attribute"); this.field = member as FieldInfo; @@ -119,7 +118,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { this.memberDeclaredType = (this.field != null) ? this.field.FieldType : this.property.PropertyType; this.defaultMemberValue = DeriveDefaultValue(this.memberDeclaredType); - Contract.Assume(this.memberDeclaredType != null); // CC missing PropertyInfo.PropertyType ensures result != null + Assumes.True(this.memberDeclaredType != null); // CC missing PropertyInfo.PropertyType ensures result != null if (attribute.Encoder == null) { if (!converters.TryGetValue(this.memberDeclaredType, out this.converter)) { if (this.memberDeclaredType.IsGenericType && @@ -203,7 +202,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> internal string StaticConstantValue { get { - Requires.ValidState(this.IsConstantValueAvailableStatically); + RequiresEx.ValidState(this.IsConstantValueAvailableStatically); return this.ToString(this.field.GetValue(null), false); } } @@ -394,7 +393,6 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <returns>An instance of the desired encoder.</returns> private static IMessagePartEncoder GetEncoder(Type messagePartEncoder) { Requires.NotNull(messagePartEncoder, "messagePartEncoder"); - Contract.Ensures(Contract.Result<IMessagePartEncoder>() != null); IMessagePartEncoder encoder; lock (encoders) { diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs index 4139f52..c45eb5d 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs @@ -6,12 +6,11 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System; - using System.Diagnostics.Contracts; + using Validation; /// <summary> /// A pair of conversion functions to map some type to a string and back again. /// </summary> - [ContractVerification(true)] internal struct ValueMapping { /// <summary> /// The mapping function that converts some custom type to a string. diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactory.cs b/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactory.cs index 762b54b..fd35e5f 100644 --- a/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactory.cs +++ b/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactory.cs @@ -7,11 +7,11 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; using System.Linq; using System.Reflection; using System.Text; using DotNetOpenAuth.Messaging.Reflection; + using Validation; /// <summary> /// A message factory that automatically selects the message type based on the incoming data. @@ -42,7 +42,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="messageTypes">The message types that this factory may instantiate.</param> public virtual void AddMessageTypes(IEnumerable<MessageDescription> messageTypes) { Requires.NotNull(messageTypes, "messageTypes"); - Requires.True(messageTypes.All(msg => msg != null), "messageTypes"); + Requires.NullOrNotNullElements(messageTypes, "messageTypes"); var unsupportedMessageTypes = new List<MessageDescription>(0); foreach (MessageDescription messageDescription in messageTypes) { @@ -208,7 +208,6 @@ namespace DotNetOpenAuth.Messaging { protected virtual IDirectedProtocolMessage InstantiateAsRequest(MessageDescription messageDescription, MessageReceivingEndpoint recipient) { Requires.NotNull(messageDescription, "messageDescription"); Requires.NotNull(recipient, "recipient"); - Contract.Ensures(Contract.Result<IDirectedProtocolMessage>() != null); ConstructorInfo ctor = this.requestMessageTypes[messageDescription]; return (IDirectedProtocolMessage)ctor.Invoke(new object[] { recipient.Location, messageDescription.MessageVersion }); @@ -223,7 +222,6 @@ namespace DotNetOpenAuth.Messaging { protected virtual IDirectResponseProtocolMessage InstantiateAsResponse(MessageDescription messageDescription, IDirectedProtocolMessage request) { Requires.NotNull(messageDescription, "messageDescription"); Requires.NotNull(request, "request"); - Contract.Ensures(Contract.Result<IDirectResponseProtocolMessage>() != null); Type requestType = request.GetType(); var ctors = this.FindMatchingResponseConstructors(messageDescription, requestType); @@ -249,7 +247,7 @@ namespace DotNetOpenAuth.Messaging { private static int GetDerivationDistance(Type assignableType, Type derivedType) { Requires.NotNull(assignableType, "assignableType"); Requires.NotNull(derivedType, "derivedType"); - Requires.True(assignableType.IsAssignableFrom(derivedType), "assignableType"); + Requires.That(assignableType.IsAssignableFrom(derivedType), "assignableType", "Types are not related as required."); // If this is the two types are equivalent... if (derivedType.IsAssignableFrom(assignableType)) @@ -277,7 +275,6 @@ namespace DotNetOpenAuth.Messaging { private static int CountInCommon(ICollection<string> collection1, ICollection<string> collection2, StringComparison comparison = StringComparison.Ordinal) { Requires.NotNull(collection1, "collection1"); Requires.NotNull(collection2, "collection2"); - Contract.Ensures(Contract.Result<int>() >= 0 && Contract.Result<int>() <= Math.Min(collection1.Count, collection2.Count)); return collection1.Count(value1 => collection2.Any(value2 => string.Equals(value1, value2, comparison))); } diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs b/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs index 7ca5d45..413d0ad 100644 --- a/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs +++ b/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs @@ -7,10 +7,10 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; using System.Linq; using System.Text; using Reflection; + using Validation; /// <summary> /// A channel that uses the standard message factory. @@ -27,16 +27,15 @@ namespace DotNetOpenAuth.Messaging { private readonly ICollection<Version> versions; /// <summary> - /// Initializes a new instance of the <see cref="StandardMessageFactoryChannel"/> class. + /// Initializes a new instance of the <see cref="StandardMessageFactoryChannel" /> class. /// </summary> /// <param name="messageTypes">The message types that might be encountered.</param> /// <param name="versions">All the possible message versions that might be encountered.</param> - /// <param name="bindingElements"> - /// The binding elements to use in sending and receiving messages. - /// The order they are provided is used for outgoing messgaes, and reversed for incoming messages. - /// </param> - protected StandardMessageFactoryChannel(ICollection<Type> messageTypes, ICollection<Version> versions, params IChannelBindingElement[] bindingElements) - : base(new StandardMessageFactory(), bindingElements) { + /// <param name="hostFactories">The host factories.</param> + /// <param name="bindingElements">The binding elements to use in sending and receiving messages. + /// The order they are provided is used for outgoing messgaes, and reversed for incoming messages.</param> + protected StandardMessageFactoryChannel(ICollection<Type> messageTypes, ICollection<Version> versions, IHostFactories hostFactories, IChannelBindingElement[] bindingElements = null) + : base(new StandardMessageFactory(), bindingElements ?? new IChannelBindingElement[0], hostFactories) { Requires.NotNull(messageTypes, "messageTypes"); Requires.NotNull(versions, "versions"); @@ -98,7 +97,6 @@ namespace DotNetOpenAuth.Messaging { { Requires.NotNull(messageTypes, "messageTypes"); Requires.NotNull(descriptionsCache, "descriptionsCache"); - Contract.Ensures(Contract.Result<IEnumerable<MessageDescription>>() != null); // Get all the MessageDescription objects through the standard cache, // so that perhaps it will be a quick lookup, or at least it will be diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs deleted file mode 100644 index 65d4827..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs +++ /dev/null @@ -1,260 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="StandardWebRequestHandler.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.IO; - using System.Net; - using System.Net.Sockets; - using System.Reflection; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// The default handler for transmitting <see cref="HttpWebRequest"/> instances - /// and returning the responses. - /// </summary> - public class StandardWebRequestHandler : IDirectWebRequestHandler { - /// <summary> - /// The set of options this web request handler supports. - /// </summary> - private const DirectWebRequestOptions SupportedOptions = DirectWebRequestOptions.AcceptAllHttpResponses; - - /// <summary> - /// The value to use for the User-Agent HTTP header. - /// </summary> - private static string userAgentValue = Assembly.GetExecutingAssembly().GetName().Name + "/" + Util.AssemblyFileVersion; - - #region IWebRequestHandler Members - - /// <summary> - /// Determines whether this instance can support the specified options. - /// </summary> - /// <param name="options">The set of options that might be given in a subsequent web request.</param> - /// <returns> - /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. - /// </returns> - [Pure] - public bool CanSupport(DirectWebRequestOptions options) { - return (options & ~SupportedOptions) == 0; - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <returns> - /// The writer the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - public Stream GetRequestStream(HttpWebRequest request) { - return this.GetRequestStream(request, DirectWebRequestOptions.None); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// The writer the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { - return GetRequestStreamCore(request); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <returns> - /// An instance of <see cref="IncomingWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - public IncomingWebResponse GetResponse(HttpWebRequest request) { - return this.GetResponse(request, DirectWebRequestOptions.None); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// An instance of <see cref="IncomingWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { - // This request MAY have already been prepared by GetRequestStream, but - // we have no guarantee, so do it just to be safe. - PrepareRequest(request, false); - - try { - Logger.Http.DebugFormat("HTTP {0} {1}", request.Method, request.RequestUri); - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - return new NetworkDirectWebResponse(request.RequestUri, response); - } catch (WebException ex) { - HttpWebResponse response = (HttpWebResponse)ex.Response; - if (response != null && response.StatusCode == HttpStatusCode.ExpectationFailed && - request.ServicePoint.Expect100Continue) { - // Some OpenID servers doesn't understand the Expect header and send 417 error back. - // If this server just failed from that, alter the ServicePoint for this server - // so that we don't send that header again next time (whenever that is). - // "Expect: 100-Continue" HTTP header. (see Google Code Issue 72) - // We don't want to blindly set all ServicePoints to not use the Expect header - // as that would be a security hole allowing any visitor to a web site change - // the web site's global behavior when calling that host. - Logger.Http.InfoFormat("HTTP POST to {0} resulted in 417 Expectation Failed. Changing ServicePoint to not use Expect: Continue next time.", request.RequestUri); - request.ServicePoint.Expect100Continue = false; // TODO: investigate that CAS may throw here - - // An alternative to ServicePoint if we don't have permission to set that, - // but we'd have to set it BEFORE each request. - ////request.Expect = ""; - } - - if ((options & DirectWebRequestOptions.AcceptAllHttpResponses) != 0 && response != null && - response.StatusCode != HttpStatusCode.ExpectationFailed) { - Logger.Http.InfoFormat("The HTTP error code {0} {1} is being accepted because the {2} flag is set.", (int)response.StatusCode, response.StatusCode, DirectWebRequestOptions.AcceptAllHttpResponses); - return new NetworkDirectWebResponse(request.RequestUri, response); - } - - if (response != null) { - Logger.Http.ErrorFormat( - "{0} returned {1} {2}: {3}", - response.ResponseUri, - (int)response.StatusCode, - response.StatusCode, - response.StatusDescription); - - if (Logger.Http.IsDebugEnabled) { - using (var reader = new StreamReader(ex.Response.GetResponseStream())) { - Logger.Http.DebugFormat( - "WebException from {0}: {1}{2}", ex.Response.ResponseUri, Environment.NewLine, reader.ReadToEnd()); - } - } - } else { - Logger.Http.ErrorFormat( - "{0} connecting to {1}", - ex.Status, - request.RequestUri); - } - - // Be sure to close the response stream to conserve resources and avoid - // filling up all our incoming pipes and denying future requests. - // If in the future, some callers actually want to read this response - // we'll need to figure out how to reliably call Close on exception - // responses at all callers. - if (response != null) { - response.Close(); - } - - throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorInRequestReplyMessage); - } - } - - #endregion - - /// <summary> - /// Determines whether an exception was thrown because of the remote HTTP server returning HTTP 417 Expectation Failed. - /// </summary> - /// <param name="ex">The caught exception.</param> - /// <returns> - /// <c>true</c> if the failure was originally caused by a 417 Exceptation Failed error; otherwise, <c>false</c>. - /// </returns> - internal static bool IsExceptionFrom417ExpectationFailed(Exception ex) { - while (ex != null) { - WebException webEx = ex as WebException; - if (webEx != null) { - HttpWebResponse response = webEx.Response as HttpWebResponse; - if (response != null) { - if (response.StatusCode == HttpStatusCode.ExpectationFailed) { - return true; - } - } - } - - ex = ex.InnerException; - } - - return false; - } - - /// <summary> - /// Initiates a POST request and prepares for sending data. - /// </summary> - /// <param name="request">The HTTP request with information about the remote party to contact.</param> - /// <returns> - /// The stream where the POST entity can be written. - /// </returns> - private static Stream GetRequestStreamCore(HttpWebRequest request) { - PrepareRequest(request, true); - - try { - return request.GetRequestStream(); - } catch (SocketException ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.WebRequestFailed, request.RequestUri); - } catch (WebException ex) { - throw ErrorUtilities.Wrap(ex, MessagingStrings.WebRequestFailed, request.RequestUri); - } - } - - /// <summary> - /// Prepares an HTTP request. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="preparingPost"><c>true</c> if this is a POST request whose headers have not yet been sent out; <c>false</c> otherwise.</param> - private static void PrepareRequest(HttpWebRequest request, bool preparingPost) { - Requires.NotNull(request, "request"); - - // Be careful to not try to change the HTTP headers that have already gone out. - if (preparingPost || request.Method == "GET") { - // Set/override a few properties of the request to apply our policies for requests. - if (Debugger.IsAttached) { - // Since a debugger is attached, requests may be MUCH slower, - // so give ourselves huge timeouts. - request.ReadWriteTimeout = (int)TimeSpan.FromHours(1).TotalMilliseconds; - request.Timeout = (int)TimeSpan.FromHours(1).TotalMilliseconds; - } - - // Some sites, such as Technorati, return 403 Forbidden on identity - // pages unless a User-Agent header is included. - if (string.IsNullOrEmpty(request.UserAgent)) { - request.UserAgent = userAgentValue; - } - } - } - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/UntrustedWebRequestHandler.cs deleted file mode 100644 index 25a7bbb..0000000 --- a/src/DotNetOpenAuth.Core/Messaging/UntrustedWebRequestHandler.cs +++ /dev/null @@ -1,476 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="UntrustedWebRequestHandler.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Messaging { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.IO; - using System.Net; - using System.Net.Cache; - using System.Text.RegularExpressions; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// A paranoid HTTP get/post request engine. It helps to protect against attacks from remote - /// server leaving dangling connections, sending too much data, causing requests against - /// internal servers, etc. - /// </summary> - /// <remarks> - /// Protections include: - /// * Conservative maximum time to receive the complete response. - /// * Only HTTP and HTTPS schemes are permitted. - /// * Internal IP address ranges are not permitted: 127.*.*.*, 1::* - /// * Internal host names are not permitted (periods must be found in the host name) - /// If a particular host would be permitted but is in the blacklist, it is not allowed. - /// If a particular host would not be permitted but is in the whitelist, it is allowed. - /// </remarks> - public class UntrustedWebRequestHandler : IDirectWebRequestHandler { - /// <summary> - /// The set of URI schemes allowed in untrusted web requests. - /// </summary> - private ICollection<string> allowableSchemes = new List<string> { "http", "https" }; - - /// <summary> - /// The collection of blacklisted hosts. - /// </summary> - private ICollection<string> blacklistHosts = new List<string>(Configuration.BlacklistHosts.KeysAsStrings); - - /// <summary> - /// The collection of regular expressions used to identify additional blacklisted hosts. - /// </summary> - private ICollection<Regex> blacklistHostsRegex = new List<Regex>(Configuration.BlacklistHostsRegex.KeysAsRegexs); - - /// <summary> - /// The collection of whitelisted hosts. - /// </summary> - private ICollection<string> whitelistHosts = new List<string>(Configuration.WhitelistHosts.KeysAsStrings); - - /// <summary> - /// The collection of regular expressions used to identify additional whitelisted hosts. - /// </summary> - private ICollection<Regex> whitelistHostsRegex = new List<Regex>(Configuration.WhitelistHostsRegex.KeysAsRegexs); - - /// <summary> - /// The maximum redirections to follow in the course of a single request. - /// </summary> - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private int maximumRedirections = Configuration.MaximumRedirections; - - /// <summary> - /// The maximum number of bytes to read from the response of an untrusted server. - /// </summary> - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private int maximumBytesToRead = Configuration.MaximumBytesToRead; - - /// <summary> - /// The handler that will actually send the HTTP request and collect - /// the response once the untrusted server gates have been satisfied. - /// </summary> - private IDirectWebRequestHandler chainedWebRequestHandler; - - /// <summary> - /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler"/> class. - /// </summary> - public UntrustedWebRequestHandler() - : this(new StandardWebRequestHandler()) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler"/> class. - /// </summary> - /// <param name="chainedWebRequestHandler">The chained web request handler.</param> - public UntrustedWebRequestHandler(IDirectWebRequestHandler chainedWebRequestHandler) { - Requires.NotNull(chainedWebRequestHandler, "chainedWebRequestHandler"); - - this.chainedWebRequestHandler = chainedWebRequestHandler; - if (Debugger.IsAttached) { - // Since a debugger is attached, requests may be MUCH slower, - // so give ourselves huge timeouts. - this.ReadWriteTimeout = TimeSpan.FromHours(1); - this.Timeout = TimeSpan.FromHours(1); - } else { - this.ReadWriteTimeout = Configuration.ReadWriteTimeout; - this.Timeout = Configuration.Timeout; - } - } - - /// <summary> - /// Gets or sets the default maximum bytes to read in any given HTTP request. - /// </summary> - /// <value>Default is 1MB. Cannot be less than 2KB.</value> - public int MaximumBytesToRead { - get { - return this.maximumBytesToRead; - } - - set { - Requires.InRange(value >= 2048, "value"); - this.maximumBytesToRead = value; - } - } - - /// <summary> - /// Gets or sets the total number of redirections to allow on any one request. - /// Default is 10. - /// </summary> - public int MaximumRedirections { - get { - return this.maximumRedirections; - } - - set { - Requires.InRange(value >= 0, "value"); - this.maximumRedirections = value; - } - } - - /// <summary> - /// Gets or sets the time allowed to wait for single read or write operation to complete. - /// Default is 500 milliseconds. - /// </summary> - public TimeSpan ReadWriteTimeout { get; set; } - - /// <summary> - /// Gets or sets the time allowed for an entire HTTP request. - /// Default is 5 seconds. - /// </summary> - public TimeSpan Timeout { get; set; } - - /// <summary> - /// Gets a collection of host name literals that should be allowed even if they don't - /// pass standard security checks. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Justification = "Spelling as intended.")] - public ICollection<string> WhitelistHosts { get { return this.whitelistHosts; } } - - /// <summary> - /// Gets a collection of host name regular expressions that indicate hosts that should - /// be allowed even though they don't pass standard security checks. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Justification = "Spelling as intended.")] - public ICollection<Regex> WhitelistHostsRegex { get { return this.whitelistHostsRegex; } } - - /// <summary> - /// Gets a collection of host name literals that should be rejected even if they - /// pass standard security checks. - /// </summary> - public ICollection<string> BlacklistHosts { get { return this.blacklistHosts; } } - - /// <summary> - /// Gets a collection of host name regular expressions that indicate hosts that should - /// be rejected even if they pass standard security checks. - /// </summary> - public ICollection<Regex> BlacklistHostsRegex { get { return this.blacklistHostsRegex; } } - - /// <summary> - /// Gets the configuration for this class that is specified in the host's .config file. - /// </summary> - private static UntrustedWebRequestElement Configuration { - get { return DotNetOpenAuthSection.Messaging.UntrustedWebRequest; } - } - - #region IDirectWebRequestHandler Members - - /// <summary> - /// Determines whether this instance can support the specified options. - /// </summary> - /// <param name="options">The set of options that might be given in a subsequent web request.</param> - /// <returns> - /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. - /// </returns> - [Pure] - public bool CanSupport(DirectWebRequestOptions options) { - // We support whatever our chained handler supports, plus RequireSsl. - return this.chainedWebRequestHandler.CanSupport(options & ~DirectWebRequestOptions.RequireSsl); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// The writer the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { - this.EnsureAllowableRequestUri(request.RequestUri, (options & DirectWebRequestOptions.RequireSsl) != 0); - - this.PrepareRequest(request, true); - - // Submit the request and get the request stream back. - return this.chainedWebRequestHandler.GetRequestStream(request, options & ~DirectWebRequestOptions.RequireSsl); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// An instance of <see cref="CachedDirectWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] - public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { - // This request MAY have already been prepared by GetRequestStream, but - // we have no guarantee, so do it just to be safe. - this.PrepareRequest(request, false); - - // Since we may require SSL for every redirect, we handle each redirect manually - // in order to detect and fail if any redirect sends us to an HTTP url. - // We COULD allow automatic redirect in the cases where HTTPS is not required, - // but our mock request infrastructure can't do redirects on its own either. - Uri originalRequestUri = request.RequestUri; - int i; - for (i = 0; i < this.MaximumRedirections; i++) { - this.EnsureAllowableRequestUri(request.RequestUri, (options & DirectWebRequestOptions.RequireSsl) != 0); - CachedDirectWebResponse response = this.chainedWebRequestHandler.GetResponse(request, options & ~DirectWebRequestOptions.RequireSsl).GetSnapshot(this.MaximumBytesToRead); - if (response.Status == HttpStatusCode.MovedPermanently || - response.Status == HttpStatusCode.Redirect || - response.Status == HttpStatusCode.RedirectMethod || - response.Status == HttpStatusCode.RedirectKeepVerb) { - // We have no copy of the post entity stream to repeat on our manually - // cloned HttpWebRequest, so we have to bail. - ErrorUtilities.VerifyProtocol(request.Method != "POST", MessagingStrings.UntrustedRedirectsOnPOSTNotSupported); - Uri redirectUri = new Uri(response.FinalUri, response.Headers[HttpResponseHeader.Location]); - request = request.Clone(redirectUri); - } else { - if (response.FinalUri != request.RequestUri) { - // Since we don't automatically follow redirects, there's only one scenario where this - // can happen: when the server sends a (non-redirecting) Content-Location header in the response. - // It's imperative that we do not trust that header though, so coerce the FinalUri to be - // what we just requested. - Logger.Http.WarnFormat("The response from {0} included an HTTP header indicating it's the same as {1}, but it's not a redirect so we won't trust that.", request.RequestUri, response.FinalUri); - response.FinalUri = request.RequestUri; - } - - return response; - } - } - - throw ErrorUtilities.ThrowProtocol(MessagingStrings.TooManyRedirects, originalRequestUri); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <returns> - /// The writer the caller should write out the entity data to. - /// </returns> - Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request) { - return this.GetRequestStream(request, DirectWebRequestOptions.None); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <returns> - /// An instance of <see cref="IncomingWebResponse"/> describing the response. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) { - return this.GetResponse(request, DirectWebRequestOptions.None); - } - - #endregion - - /// <summary> - /// Determines whether an IP address is the IPv6 equivalent of "localhost/127.0.0.1". - /// </summary> - /// <param name="ip">The ip address to check.</param> - /// <returns> - /// <c>true</c> if this is a loopback IP address; <c>false</c> otherwise. - /// </returns> - private static bool IsIPv6Loopback(IPAddress ip) { - Requires.NotNull(ip, "ip"); - byte[] addressBytes = ip.GetAddressBytes(); - for (int i = 0; i < addressBytes.Length - 1; i++) { - if (addressBytes[i] != 0) { - return false; - } - } - if (addressBytes[addressBytes.Length - 1] != 1) { - return false; - } - return true; - } - - /// <summary> - /// Determines whether the given host name is in a host list or host name regex list. - /// </summary> - /// <param name="host">The host name.</param> - /// <param name="stringList">The list of host names.</param> - /// <param name="regexList">The list of regex patterns of host names.</param> - /// <returns> - /// <c>true</c> if the specified host falls within at least one of the given lists; otherwise, <c>false</c>. - /// </returns> - private static bool IsHostInList(string host, ICollection<string> stringList, ICollection<Regex> regexList) { - Requires.NotNullOrEmpty(host, "host"); - Requires.NotNull(stringList, "stringList"); - Requires.NotNull(regexList, "regexList"); - foreach (string testHost in stringList) { - if (string.Equals(host, testHost, StringComparison.OrdinalIgnoreCase)) { - return true; - } - } - foreach (Regex regex in regexList) { - if (regex.IsMatch(host)) { - return true; - } - } - return false; - } - - /// <summary> - /// Determines whether a given host is whitelisted. - /// </summary> - /// <param name="host">The host name to test.</param> - /// <returns> - /// <c>true</c> if the host is whitelisted; otherwise, <c>false</c>. - /// </returns> - private bool IsHostWhitelisted(string host) { - return IsHostInList(host, this.WhitelistHosts, this.WhitelistHostsRegex); - } - - /// <summary> - /// Determines whether a given host is blacklisted. - /// </summary> - /// <param name="host">The host name to test.</param> - /// <returns> - /// <c>true</c> if the host is blacklisted; otherwise, <c>false</c>. - /// </returns> - private bool IsHostBlacklisted(string host) { - return IsHostInList(host, this.BlacklistHosts, this.BlacklistHostsRegex); - } - - /// <summary> - /// Verify that the request qualifies under our security policies - /// </summary> - /// <param name="requestUri">The request URI.</param> - /// <param name="requireSsl">If set to <c>true</c>, only web requests that can be made entirely over SSL will succeed.</param> - /// <exception cref="ProtocolException">Thrown when the URI is disallowed for security reasons.</exception> - private void EnsureAllowableRequestUri(Uri requestUri, bool requireSsl) { - ErrorUtilities.VerifyProtocol(this.IsUriAllowable(requestUri), MessagingStrings.UnsafeWebRequestDetected, requestUri); - ErrorUtilities.VerifyProtocol(!requireSsl || string.Equals(requestUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase), MessagingStrings.InsecureWebRequestWithSslRequired, requestUri); - } - - /// <summary> - /// Determines whether a URI is allowed based on scheme and host name. - /// No requireSSL check is done here - /// </summary> - /// <param name="uri">The URI to test for whether it should be allowed.</param> - /// <returns> - /// <c>true</c> if [is URI allowable] [the specified URI]; otherwise, <c>false</c>. - /// </returns> - private bool IsUriAllowable(Uri uri) { - Requires.NotNull(uri, "uri"); - if (!this.allowableSchemes.Contains(uri.Scheme)) { - Logger.Http.WarnFormat("Rejecting URL {0} because it uses a disallowed scheme.", uri); - return false; - } - - // Allow for whitelist or blacklist to override our detection. - Func<string, bool> failsUnlessWhitelisted = (string reason) => { - if (IsHostWhitelisted(uri.DnsSafeHost)) { - return true; - } - Logger.Http.WarnFormat("Rejecting URL {0} because {1}.", uri, reason); - return false; - }; - - // Try to interpret the hostname as an IP address so we can test for internal - // IP address ranges. Note that IP addresses can appear in many forms - // (e.g. http://127.0.0.1, http://2130706433, http://0x0100007f, http://::1 - // So we convert them to a canonical IPAddress instance, and test for all - // non-routable IP ranges: 10.*.*.*, 127.*.*.*, ::1 - // Note that Uri.IsLoopback is very unreliable, not catching many of these variants. - IPAddress hostIPAddress; - if (IPAddress.TryParse(uri.DnsSafeHost, out hostIPAddress)) { - byte[] addressBytes = hostIPAddress.GetAddressBytes(); - - // The host is actually an IP address. - switch (hostIPAddress.AddressFamily) { - case System.Net.Sockets.AddressFamily.InterNetwork: - if (addressBytes[0] == 127 || addressBytes[0] == 10) { - return failsUnlessWhitelisted("it is a loopback address."); - } - break; - case System.Net.Sockets.AddressFamily.InterNetworkV6: - if (IsIPv6Loopback(hostIPAddress)) { - return failsUnlessWhitelisted("it is a loopback address."); - } - break; - default: - return failsUnlessWhitelisted("it does not use an IPv4 or IPv6 address."); - } - } else { - // The host is given by name. We require names to contain periods to - // help make sure it's not an internal address. - if (!uri.Host.Contains(".")) { - return failsUnlessWhitelisted("it does not contain a period in the host name."); - } - } - if (this.IsHostBlacklisted(uri.DnsSafeHost)) { - Logger.Http.WarnFormat("Rejected URL {0} because it is blacklisted.", uri); - return false; - } - return true; - } - - /// <summary> - /// Prepares the request by setting timeout and redirect policies. - /// </summary> - /// <param name="request">The request to prepare.</param> - /// <param name="preparingPost"><c>true</c> if this is a POST request whose headers have not yet been sent out; <c>false</c> otherwise.</param> - private void PrepareRequest(HttpWebRequest request, bool preparingPost) { - Requires.NotNull(request, "request"); - - // Be careful to not try to change the HTTP headers that have already gone out. - if (preparingPost || request.Method == "GET") { - // Set/override a few properties of the request to apply our policies for untrusted requests. - request.ReadWriteTimeout = (int)this.ReadWriteTimeout.TotalMilliseconds; - request.Timeout = (int)this.Timeout.TotalMilliseconds; - request.KeepAlive = false; - } - - // If SSL is required throughout, we cannot allow auto redirects because - // it may include a pass through an unprotected HTTP request. - // We have to follow redirects manually. - // It also allows us to ignore HttpWebResponse.FinalUri since that can be affected by - // the Content-Location header and open security holes. - request.AllowAutoRedirect = false; - } - } -} diff --git a/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs b/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs index 242175e..1d7c424 100644 --- a/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs +++ b/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs @@ -7,7 +7,6 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -15,6 +14,7 @@ namespace DotNetOpenAuth.Messaging { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.Messaging.Reflection; + using Validation; /// <summary> /// A serializer for <see cref="DataBag"/>-derived types @@ -46,7 +46,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> protected internal UriStyleMessageFormatter(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) : base(cryptoKeyStore, bucket, signed, encrypted, compressed, minimumAge, maximumAge, decodeOnceOnly) { - Requires.True((cryptoKeyStore != null && !string.IsNullOrEmpty(bucket)) || (!signed && !encrypted), null); + Requires.That((cryptoKeyStore != null && !string.IsNullOrEmpty(bucket)) || (!signed && !encrypted), null, "Signing or encryption requires a cryptoKeyStore and bucket."); } /// <summary> diff --git a/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs index 21cbb94..9a98c9b 100644 --- a/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs +++ b/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs @@ -7,7 +7,6 @@ // We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. using System; -using System.Diagnostics.Contracts; using System.Net; using System.Reflection; using System.Resources; @@ -31,8 +30,6 @@ using System.Web.UI; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] -[assembly: ContractVerification(true)] - #if StrongNameSigned // See comment at top of this file. We need this so that strong-naming doesn't // keep this assembly from being useful to shared host (medium trust) web sites. @@ -65,6 +62,7 @@ using System.Web.UI; [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] #else [assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.Core.UI")] [assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard")] [assembly: InternalsVisibleTo("DotNetOpenAuth.InfoCard.UI")] [assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId")] @@ -84,6 +82,7 @@ using System.Web.UI; [assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ClientAuthorization")] [assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer")] [assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client.UI")] [assembly: InternalsVisibleTo("DotNetOpenAuth.AspNet.Test")] [assembly: InternalsVisibleTo("DotNetOpenAuth.AspNet")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/DotNetOpenAuth.Core/Reporting.cs b/src/DotNetOpenAuth.Core/Reporting.cs index 80a3374..432a833 100644 --- a/src/DotNetOpenAuth.Core/Reporting.cs +++ b/src/DotNetOpenAuth.Core/Reporting.cs @@ -9,20 +9,23 @@ namespace DotNetOpenAuth { using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.IO.IsolatedStorage; using System.Linq; using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; using System.Reflection; using System.Security; using System.Text; using System.Threading; + using System.Threading.Tasks; using System.Web; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; + using Validation; /// <summary> /// The statistical reporting mechanism used so this library's project authors @@ -71,11 +74,6 @@ namespace DotNetOpenAuth { private static Uri wellKnownPostLocation = new Uri("https://reports.dotnetopenauth.net/ReportingPost.ashx"); /// <summary> - /// The outgoing HTTP request handler to use for publishing reports. - /// </summary> - private static IDirectWebRequestHandler webRequestHandler; - - /// <summary> /// A few HTTP request hosts and paths we've seen. /// </summary> private static PersistentHashSet observedRequests; @@ -170,7 +168,7 @@ namespace DotNetOpenAuth { /// <param name="category">The category within the event. Null and empty strings are allowed, but considered the same.</param> [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "PersistentCounter instances are stored in a table for later use.")] internal static void RecordEventOccurrence(string eventName, string category) { - Contract.Requires(!string.IsNullOrEmpty(eventName)); + Requires.NotNullOrEmpty(eventName, "eventName"); // In release builds, just quietly return. if (string.IsNullOrEmpty(eventName)) { @@ -196,7 +194,7 @@ namespace DotNetOpenAuth { /// <param name="eventNameByObjectType">The object whose type name is the event name to record.</param> /// <param name="category">The category within the event. Null and empty strings are allowed, but considered the same.</param> internal static void RecordEventOccurrence(object eventNameByObjectType, string category) { - Contract.Requires(eventNameByObjectType != null); + Requires.NotNull(eventNameByObjectType, "eventNameByObjectType"); // In release builds, just quietly return. if (eventNameByObjectType == null) { @@ -213,7 +211,7 @@ namespace DotNetOpenAuth { /// </summary> /// <param name="feature">The feature.</param> internal static void RecordFeatureUse(string feature) { - Contract.Requires(!string.IsNullOrEmpty(feature)); + Requires.NotNullOrEmpty(feature, "feature"); // In release builds, just quietly return. if (string.IsNullOrEmpty(feature)) { @@ -231,7 +229,7 @@ namespace DotNetOpenAuth { /// </summary> /// <param name="value">The object whose type is the feature to set as used.</param> internal static void RecordFeatureUse(object value) { - Contract.Requires(value != null); + Requires.NotNull(value, "value"); // In release builds, just quietly return. if (value == null) { @@ -250,7 +248,7 @@ namespace DotNetOpenAuth { /// <param name="value">The object whose type is the feature to set as used.</param> /// <param name="dependency1">Some dependency used by <paramref name="value"/>.</param> internal static void RecordFeatureAndDependencyUse(object value, object dependency1) { - Contract.Requires(value != null); + Requires.NotNull(value, "value"); // In release builds, just quietly return. if (value == null) { @@ -274,7 +272,7 @@ namespace DotNetOpenAuth { /// <param name="dependency1">Some dependency used by <paramref name="value"/>.</param> /// <param name="dependency2">Some dependency used by <paramref name="value"/>.</param> internal static void RecordFeatureAndDependencyUse(object value, object dependency1, object dependency2) { - Contract.Requires(value != null); + Requires.NotNull(value, "value"); // In release builds, just quietly return. if (value == null) { @@ -298,7 +296,7 @@ namespace DotNetOpenAuth { /// </summary> /// <param name="request">The request.</param> internal static void RecordRequestStatistics(HttpRequestBase request) { - Contract.Requires(request != null); + Requires.NotNull(request, "request"); // In release builds, just quietly return. if (request == null) { @@ -330,7 +328,7 @@ namespace DotNetOpenAuth { lock (publishingConsiderationLock) { if (DateTime.Now - lastPublished > Configuration.MinimumReportingInterval) { lastPublished = DateTime.Now; - SendStatsAsync(); + var fireAndForget = SendStatsAsync(); } } } @@ -346,7 +344,6 @@ namespace DotNetOpenAuth { file = GetIsolatedStorage(); reportOriginIdentity = GetOrCreateOriginIdentity(); - webRequestHandler = new StandardWebRequestHandler(); observations.Add(observedRequests = new PersistentHashSet(file, "requests.txt", 3)); observations.Add(observedCultures = new PersistentHashSet(file, "cultures.txt", 20)); observations.Add(observedFeatures = new PersistentHashSet(file, "features.txt", int.MaxValue)); @@ -371,6 +368,18 @@ namespace DotNetOpenAuth { } /// <summary> + /// Creates an HTTP client that can be used for outbound HTTP requests. + /// </summary> + /// <returns>The HTTP client to use.</returns> + private static HttpClient CreateHttpClient() { + var channel = new HttpClientHandler(); + channel.AllowAutoRedirect = false; + var webRequestHandler = new HttpClient(channel); + webRequestHandler.DefaultRequestHeaders.UserAgent.Add(Util.LibraryVersionHeader); + return webRequestHandler; + } + + /// <summary> /// Assembles a report for submission. /// </summary> /// <returns>A stream that contains the report.</returns> @@ -426,30 +435,24 @@ namespace DotNetOpenAuth { /// Sends the usage reports to the library authors. /// </summary> /// <returns>A value indicating whether submitting the report was successful.</returns> - private static bool SendStats() { + private static async Task<bool> SendStatsAsync() { try { - var request = (HttpWebRequest)WebRequest.Create(wellKnownPostLocation); - request.UserAgent = Util.LibraryVersion; - request.AllowAutoRedirect = false; - request.Method = "POST"; - request.ContentType = "text/dnoa-report1"; Stream report = GetReport(); - request.ContentLength = report.Length; - using (var requestStream = webRequestHandler.GetRequestStream(request)) { - report.CopyTo(requestStream); - } - - using (var response = webRequestHandler.GetResponse(request)) { - Logger.Library.Info("Statistical report submitted successfully."); - - // The response stream may contain a message for the webmaster. - // Since as part of the report we submit the library version number, - // the report receiving service may have alerts such as: - // "You're using an obsolete version with exploitable security vulnerabilities." - using (var responseReader = response.GetResponseReader()) { - string line = responseReader.ReadLine(); - if (line != null) { - DemuxLogMessage(line); + var content = new StreamContent(report); + content.Headers.ContentType = new MediaTypeHeaderValue("text/dnoa-report1"); + using (var webRequestHandler = CreateHttpClient()) { + using (var response = await webRequestHandler.PostAsync(wellKnownPostLocation, content)) { + Logger.Library.Info("Statistical report submitted successfully."); + + // The response stream may contain a message for the webmaster. + // Since as part of the report we submit the library version number, + // the report receiving service may have alerts such as: + // "You're using an obsolete version with exploitable security vulnerabilities." + using (var responseReader = new StreamReader(await response.Content.ReadAsStreamAsync())) { + string line = await responseReader.ReadLineAsync(); + if (line != null) { + DemuxLogMessage(line); + } } } } @@ -508,34 +511,10 @@ namespace DotNetOpenAuth { } /// <summary> - /// Sends the stats report asynchronously, and careful to not throw any unhandled exceptions. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Unhandled exceptions MUST NOT be thrown from here.")] - private static void SendStatsAsync() { - // Do it on a background thread since it could take a while and we - // don't want to slow down this request we're borrowing. - ThreadPool.QueueUserWorkItem(state => { - try { - SendStats(); - } catch (Exception ex) { - // Something bad and unexpected happened. Just deactivate to avoid more trouble. - try { - broken = true; - Logger.Library.Error("Error while trying to submit statistical report.", ex); - } catch (Exception) { - // swallow exceptions to prevent a crash. - } - } - }); - } - - /// <summary> /// Gets the isolated storage to use for reporting. /// </summary> /// <returns>An isolated storage location appropriate for our host.</returns> private static IsolatedStorageFile GetIsolatedStorage() { - Contract.Ensures(Contract.Result<IsolatedStorageFile>() != null); - IsolatedStorageFile result = null; // We'll try for whatever storage location we can get, @@ -567,8 +546,7 @@ namespace DotNetOpenAuth { /// </remarks> [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")] private static Guid GetOrCreateOriginIdentity() { - Requires.ValidState(file != null); - Contract.Ensures(Contract.Result<Guid>() != Guid.Empty); + RequiresEx.ValidState(file != null, "file not set."); Guid identityGuid = Guid.Empty; const int GuidLength = 16; diff --git a/src/DotNetOpenAuth.Core/Requires.cs b/src/DotNetOpenAuth.Core/Requires.cs deleted file mode 100644 index 7d4d5be..0000000 --- a/src/DotNetOpenAuth.Core/Requires.cs +++ /dev/null @@ -1,255 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Requires.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.Contracts; - using System.Globalization; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Argument validation checks that throw some kind of ArgumentException when they fail (unless otherwise noted). - /// </summary> - internal static class Requires { - /// <summary> - /// Validates that a given parameter is not null. - /// </summary> - /// <typeparam name="T">The type of the parameter</typeparam> - /// <param name="value">The value.</param> - /// <param name="parameterName">Name of the parameter.</param> - /// <returns>The tested value, guaranteed to not be null.</returns> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static T NotNull<T>(T value, string parameterName) where T : class { - if (value == null) { - throw new ArgumentNullException(parameterName); - } - - Contract.EndContractBlock(); - return value; - } - - /// <summary> - /// Validates that a parameter is not null or empty. - /// </summary> - /// <param name="value">The value.</param> - /// <param name="parameterName">Name of the parameter.</param> - /// <returns>The validated value.</returns> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static string NotNullOrEmpty(string value, string parameterName) { - NotNull(value, parameterName); - True(value.Length > 0, parameterName, Strings.EmptyStringNotAllowed); - Contract.Ensures(Contract.Result<string>() == value); - Contract.EndContractBlock(); - return value; - } - - /// <summary> - /// Validates that an array is not null or empty. - /// </summary> - /// <typeparam name="T">The type of the elements in the sequence.</typeparam> - /// <param name="value">The value.</param> - /// <param name="parameterName">Name of the parameter.</param> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static void NotNullOrEmpty<T>(IEnumerable<T> value, string parameterName) { - NotNull(value, parameterName); - True(value.Any(), parameterName, Strings.InvalidArgument); - Contract.EndContractBlock(); - } - - /// <summary> - /// Validates that an argument is either null or is a sequence with no null elements. - /// </summary> - /// <typeparam name="T">The type of elements in the sequence.</typeparam> - /// <param name="sequence">The sequence.</param> - /// <param name="parameterName">Name of the parameter.</param> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static void NullOrWithNoNullElements<T>(IEnumerable<T> sequence, string parameterName) where T : class { - if (sequence != null) { - if (sequence.Any(e => e == null)) { - throw new ArgumentException(MessagingStrings.SequenceContainsNullElement, parameterName); - } - } - } - - /// <summary> - /// Validates some expression describing the acceptable range for an argument evaluates to true. - /// </summary> - /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="ArgumentOutOfRangeException"/>.</param> - /// <param name="parameterName">Name of the parameter.</param> - /// <param name="message">The message to include with the exception.</param> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static void InRange(bool condition, string parameterName, string message = null) { - if (!condition) { - throw new ArgumentOutOfRangeException(parameterName, message); - } - - Contract.EndContractBlock(); - } - - /// <summary> - /// Validates some expression describing the acceptable condition for an argument evaluates to true. - /// </summary> - /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="ArgumentException"/>.</param> - /// <param name="parameterName">Name of the parameter.</param> - /// <param name="message">The message to include with the exception.</param> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static void True(bool condition, string parameterName = null, string message = null) { - if (!condition) { - throw new ArgumentException(message ?? Strings.InvalidArgument, parameterName); - } - - Contract.EndContractBlock(); - } - - /// <summary> - /// Validates some expression describing the acceptable condition for an argument evaluates to true. - /// </summary> - /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="ArgumentException"/>.</param> - /// <param name="parameterName">Name of the parameter.</param> - /// <param name="unformattedMessage">The unformatted message.</param> - /// <param name="args">Formatting arguments.</param> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static void True(bool condition, string parameterName, string unformattedMessage, params object[] args) { - if (!condition) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, unformattedMessage, args), parameterName); - } - - Contract.EndContractBlock(); - } - - /// <summary> - /// Validates some expression describing the acceptable condition for an argument evaluates to true. - /// </summary> - /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="InvalidOperationException"/>.</param> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static void ValidState(bool condition) { - if (!condition) { - throw new InvalidOperationException(); - } - - Contract.EndContractBlock(); - } - - /// <summary> - /// Validates some expression describing the acceptable condition for an argument evaluates to true. - /// </summary> - /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="InvalidOperationException"/>.</param> - /// <param name="message">The message to include with the exception.</param> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static void ValidState(bool condition, string message) { - if (!condition) { - throw new InvalidOperationException(message); - } - - Contract.EndContractBlock(); - } - - /// <summary> - /// Validates some expression describing the acceptable condition for an argument evaluates to true. - /// </summary> - /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="InvalidOperationException"/>.</param> - /// <param name="unformattedMessage">The unformatted message.</param> - /// <param name="args">Formatting arguments.</param> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static void ValidState(bool condition, string unformattedMessage, params object[] args) { - if (!condition) { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, unformattedMessage, args)); - } - - Contract.EndContractBlock(); - } - - /// <summary> - /// Validates that some argument describes a type that is or derives from a required type. - /// </summary> - /// <typeparam name="T">The type that the argument must be or derive from.</typeparam> - /// <param name="type">The type given in the argument.</param> - /// <param name="parameterName">Name of the parameter.</param> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static void NotNullSubtype<T>(Type type, string parameterName) { - NotNull(type, parameterName); - True(typeof(T).IsAssignableFrom(type), parameterName, MessagingStrings.UnexpectedType, typeof(T).FullName, type.FullName); - - Contract.EndContractBlock(); - } - - /// <summary> - /// Validates some expression describing the acceptable condition for an argument evaluates to true. - /// </summary> - /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="FormatException"/>.</param> - /// <param name="message">The message.</param> -#if !CLR4 - [ContractArgumentValidator] -#endif - [Pure, DebuggerStepThrough] - internal static void Format(bool condition, string message) { - if (!condition) { - throw new FormatException(message); - } - - Contract.EndContractBlock(); - } - - /// <summary> - /// Throws an <see cref="NotSupportedException"/> if a condition does not evaluate to <c>true</c>. - /// </summary> - /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="NotSupportedException"/>.</param> - /// <param name="message">The message.</param> - [Pure, DebuggerStepThrough] - internal static void Support(bool condition, string message) { - if (!condition) { - throw new NotSupportedException(message); - } - } - - /// <summary> - /// Throws an <see cref="ArgumentException"/> - /// </summary> - /// <param name="parameterName">Name of the parameter.</param> - /// <param name="message">The message.</param> - [Pure, DebuggerStepThrough] - internal static void Fail(string parameterName, string message) { - throw new ArgumentException(message, parameterName); - } - } -} diff --git a/src/DotNetOpenAuth.Core/RequiresEx.cs b/src/DotNetOpenAuth.Core/RequiresEx.cs new file mode 100644 index 0000000..1a077c4 --- /dev/null +++ b/src/DotNetOpenAuth.Core/RequiresEx.cs @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------- +// <copyright file="RequiresEx.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using Validation; + + /// <summary> + /// Argument validation checks that throw some kind of ArgumentException when they fail (unless otherwise noted). + /// </summary> + internal static class RequiresEx { + /// <summary> + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="InvalidOperationException"/>.</param> + [Pure, DebuggerStepThrough] + internal static void ValidState(bool condition) { + if (!condition) { + throw new InvalidOperationException(); + } + } + + /// <summary> + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="InvalidOperationException"/>.</param> + /// <param name="message">The message to include with the exception.</param> + [Pure, DebuggerStepThrough] + internal static void ValidState(bool condition, string message) { + if (!condition) { + throw new InvalidOperationException(message); + } + } + + /// <summary> + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="InvalidOperationException"/>.</param> + /// <param name="unformattedMessage">The unformatted message.</param> + /// <param name="args">Formatting arguments.</param> + [Pure, DebuggerStepThrough] + internal static void ValidState(bool condition, string unformattedMessage, params object[] args) { + if (!condition) { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, unformattedMessage, args)); + } + } + + /// <summary> + /// Validates that some argument describes a type that is or derives from a required type. + /// </summary> + /// <typeparam name="T">The type that the argument must be or derive from.</typeparam> + /// <param name="type">The type given in the argument.</param> + /// <param name="parameterName">Name of the parameter.</param> + [Pure, DebuggerStepThrough] + internal static void NotNullSubtype<T>(Type type, string parameterName) { + Requires.NotNull(type, parameterName); + Requires.That(typeof(T).IsAssignableFrom(type), parameterName, MessagingStrings.UnexpectedType, typeof(T).FullName, type.FullName); + } + + /// <summary> + /// Validates some expression describing the acceptable condition for an argument evaluates to true. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="FormatException"/>.</param> + /// <param name="message">The message.</param> + [Pure, DebuggerStepThrough] + internal static void Format(bool condition, string message) { + if (!condition) { + throw new FormatException(message); + } + } + + /// <summary> + /// Throws an <see cref="NotSupportedException"/> if a condition does not evaluate to <c>true</c>. + /// </summary> + /// <param name="condition">The expression that must evaluate to true to avoid an <see cref="NotSupportedException"/>.</param> + /// <param name="message">The message.</param> + [Pure, DebuggerStepThrough] + internal static void Support(bool condition, string message) { + if (!condition) { + throw new NotSupportedException(message); + } + } + } +} diff --git a/src/DotNetOpenAuth.Core/Strings.Designer.cs b/src/DotNetOpenAuth.Core/Strings.Designer.cs index b0e66d2..b96f68a 100644 --- a/src/DotNetOpenAuth.Core/Strings.Designer.cs +++ b/src/DotNetOpenAuth.Core/Strings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.17622 +// Runtime Version:4.0.30319.18033 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -97,6 +97,24 @@ namespace DotNetOpenAuth { } /// <summary> + /// Looks up a localized string similar to The provided data could not be decrypted. If the current application is deployed in a web farm configuration, ensure that the 'decryptionKey' and 'validationKey' attributes are explicitly specified in the <machineKey> configuration section.. + /// </summary> + internal static string Generic_CryptoFailure { + get { + return ResourceManager.GetString("Generic_CryptoFailure", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The HostFactories property must be set first.. + /// </summary> + internal static string HostFactoriesRequired { + get { + return ResourceManager.GetString("HostFactoriesRequired", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The argument has an unexpected value.. /// </summary> internal static string InvalidArgument { diff --git a/src/DotNetOpenAuth.Core/Strings.resx b/src/DotNetOpenAuth.Core/Strings.resx index f4d61d1..1c2aee2 100644 --- a/src/DotNetOpenAuth.Core/Strings.resx +++ b/src/DotNetOpenAuth.Core/Strings.resx @@ -141,4 +141,10 @@ <data name="ResponseBodyNotSupported" xml:space="preserve"> <value>This object contains a response body, which is not supported.</value> </data> + <data name="HostFactoriesRequired" xml:space="preserve"> + <value>The HostFactories property must be set first.</value> + </data> + <data name="Generic_CryptoFailure" xml:space="preserve"> + <value>The provided data could not be decrypted. If the current application is deployed in a web farm configuration, ensure that the 'decryptionKey' and 'validationKey' attributes are explicitly specified in the <machineKey> configuration section.</value> + </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Core/UriUtil.cs b/src/DotNetOpenAuth.Core/UriUtil.cs index c52e1bb..25b92a2 100644 --- a/src/DotNetOpenAuth.Core/UriUtil.cs +++ b/src/DotNetOpenAuth.Core/UriUtil.cs @@ -8,17 +8,16 @@ namespace DotNetOpenAuth { using System; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; using System.Linq; using System.Text.RegularExpressions; using System.Web; using System.Web.UI; using DotNetOpenAuth.Messaging; + using Validation; /// <summary> /// Utility methods for working with URIs. /// </summary> - [ContractVerification(true)] internal static class UriUtil { /// <summary> /// Tests a URI for the presence of an OAuth payload. @@ -28,15 +27,14 @@ namespace DotNetOpenAuth { /// <returns> /// True if the URI contains an OAuth message. /// </returns> - [ContractVerification(false)] // bugs/limitations in CC static analysis - internal static bool QueryStringContainPrefixedParameters(this Uri uri, string prefix) { + internal static bool QueryStringContainPrefixedParameters(this Uri uri, string prefix) { Requires.NotNullOrEmpty(prefix, "prefix"); if (uri == null) { return false; } NameValueCollection nvc = HttpUtility.ParseQueryString(uri.Query); - Contract.Assume(nvc != null); // BCL + Assumes.True(nvc != null); // BCL return nvc.Keys.OfType<string>().Any(key => key.StartsWith(prefix, StringComparison.Ordinal)); } @@ -60,7 +58,6 @@ namespace DotNetOpenAuth { /// <returns>The string version of the Uri.</returns> internal static string ToStringWithImpliedPorts(this UriBuilder builder) { Requires.NotNull(builder, "builder"); - Contract.Ensures(Contract.Result<string>() != null); // We only check for implied ports on HTTP and HTTPS schemes since those // are the only ones supported by OpenID anyway. @@ -73,7 +70,7 @@ namespace DotNetOpenAuth { // we're removing only the port (and not something in the query string that // looks like a port. string result = Regex.Replace(url, @"^(https?://[^:]+):\d+", m => m.Groups[1].Value, RegexOptions.IgnoreCase); - Contract.Assume(result != null); // Regex.Replace never returns null + Assumes.True(result != null); // Regex.Replace never returns null return result; } else { // The port must be explicitly given anyway. @@ -95,12 +92,12 @@ namespace DotNetOpenAuth { } if (page != null && !designMode) { - Contract.Assume(page.Request != null); + Assumes.True(page.Request != null); // Validate new value by trying to construct a Realm object based on it. string relativeUrl = page.ResolveUrl(value); - Contract.Assume(page.Request.Url != null); - Contract.Assume(relativeUrl != null); + Assumes.True(page.Request.Url != null); + Assumes.True(relativeUrl != null); new Uri(page.Request.Url, relativeUrl); // throws an exception on failure. } else { // We can't fully test it, but it should start with either ~/ or a protocol. diff --git a/src/DotNetOpenAuth.Core/Util.cs b/src/DotNetOpenAuth.Core/Util.cs index 26b7b45..00d033f 100644 --- a/src/DotNetOpenAuth.Core/Util.cs +++ b/src/DotNetOpenAuth.Core/Util.cs @@ -6,22 +6,23 @@ namespace DotNetOpenAuth { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; using System.Globalization; + using System.Linq; using System.Net; + using System.Net.Http.Headers; using System.Reflection; using System.Text; + using System.Threading.Tasks; using System.Web; using System.Web.UI; - using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Reflection; + using Validation; /// <summary> /// A grab-bag utility class. /// </summary> - [ContractVerification(true)] internal static class Util { /// <summary> /// The base namespace for this library from which all other namespaces derive. @@ -29,24 +30,44 @@ namespace DotNetOpenAuth { internal const string DefaultNamespace = "DotNetOpenAuth"; /// <summary> + /// A lazily-assembled string that describes the version of the library. + /// </summary> + private static readonly Lazy<string> libraryVersionLazy = new Lazy<string>(delegate { + var assembly = Assembly.GetExecutingAssembly(); + string assemblyFullName = assembly.FullName; + bool official = assemblyFullName.Contains("PublicKeyToken=2780ccd10d57b246"); + assemblyFullName = assemblyFullName.Replace(assembly.GetName().Version.ToString(), AssemblyFileVersion); + + // We use InvariantCulture since this is used for logging. + return string.Format(CultureInfo.InvariantCulture, "{0} ({1})", assemblyFullName, official ? "official" : "private"); + }); + + /// <summary> + /// A lazily-assembled string that describes the version of the library. + /// </summary> + private static readonly Lazy<ProductInfoHeaderValue> libraryVersionHeaderLazy = new Lazy<ProductInfoHeaderValue>(delegate { + var assemblyName = Assembly.GetExecutingAssembly().GetName(); + return new ProductInfoHeaderValue(assemblyName.Name, AssemblyFileVersion); + }); + + /// <summary> /// The web.config file-specified provider of web resource URLs. /// </summary> - private static IEmbeddedResourceRetrieval embeddedResourceRetrieval = MessagingElement.Configuration.EmbeddedResourceRetrievalProvider.CreateInstance(null, false); + private static IEmbeddedResourceRetrieval embeddedResourceRetrieval = MessagingElement.Configuration.EmbeddedResourceRetrievalProvider.CreateInstance(null, false, null); /// <summary> /// Gets a human-readable description of the library name and version, including /// whether the build is an official or private one. /// </summary> internal static string LibraryVersion { - get { - var assembly = Assembly.GetExecutingAssembly(); - string assemblyFullName = assembly.FullName; - bool official = assemblyFullName.Contains("PublicKeyToken=2780ccd10d57b246"); - assemblyFullName = assemblyFullName.Replace(assembly.GetName().Version.ToString(), AssemblyFileVersion); + get { return libraryVersionLazy.Value; } + } - // We use InvariantCulture since this is used for logging. - return string.Format(CultureInfo.InvariantCulture, "{0} ({1})", assemblyFullName, official ? "official" : "private"); - } + /// <summary> + /// Gets an HTTP header that can be included in outbound requests. + /// </summary> + internal static ProductInfoHeaderValue LibraryVersionHeader { + get { return libraryVersionHeaderLazy.Value; } } /// <summary> @@ -103,8 +124,7 @@ namespace DotNetOpenAuth { return new DelayedToString<IEnumerable<KeyValuePair<K, V>>>( pairs, p => { - ////Contract.Requires(pairs != null); // CC: anonymous method can't handle it - ErrorUtilities.VerifyArgumentNotNull(pairs, "pairs"); + Requires.NotNull(pairs, "pairs"); var dictionary = pairs as IDictionary<K, V>; var messageDictionary = pairs as MessageDictionary; StringBuilder sb = new StringBuilder(dictionary != null ? dictionary.Count * 40 : 200); @@ -139,7 +159,6 @@ namespace DotNetOpenAuth { /// <param name="list">The list of elements.</param> /// <param name="multiLineElements">if set to <c>true</c>, special formatting will be applied to the output to make it clear where one element ends and the next begins.</param> /// <returns>An object whose ToString method will perform the actual work of generating the string.</returns> - [ContractVerification(false)] internal static object ToStringDeferred<T>(this IEnumerable<T> list, bool multiLineElements) { return new DelayedToString<IEnumerable<T>>( list, @@ -148,7 +167,7 @@ namespace DotNetOpenAuth { ErrorUtilities.VerifyArgumentNotNull(l, "l"); string newLine = Environment.NewLine; - ////Contract.Assume(newLine != null && newLine.Length > 0); + ////Assumes.True(newLine != null && newLine.Length > 0); StringBuilder sb = new StringBuilder(); if (multiLineElements) { sb.AppendLine("[{"); @@ -215,6 +234,22 @@ namespace DotNetOpenAuth { } /// <summary> + /// Creates a dictionary of a sequence of elements and the result of an asynchronous transform, + /// allowing the async work to proceed concurrently. + /// </summary> + /// <typeparam name="TSource">The type of the source.</typeparam> + /// <typeparam name="TResult">The type of the result.</typeparam> + /// <param name="source">The source.</param> + /// <param name="transform">The transform.</param> + /// <returns>A dictionary populated with the results of the transforms.</returns> + internal static async Task<Dictionary<TSource, TResult>> ToDictionaryAsync<TSource, TResult>( + this IEnumerable<TSource> source, Func<TSource, Task<TResult>> transform) { + var taskResults = source.ToDictionary(s => s, transform); + await Task.WhenAll(taskResults.Values); + return taskResults.ToDictionary(p => p.Key, p => p.Value.Result); + } + + /// <summary> /// Manages an individual deferred ToString call. /// </summary> /// <typeparam name="T">The type of object to be serialized as a string.</typeparam> diff --git a/src/DotNetOpenAuth.Core/packages.config b/src/DotNetOpenAuth.Core/packages.config new file mode 100644 index 0000000..ba1d0bf --- /dev/null +++ b/src/DotNetOpenAuth.Core/packages.config @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="log4net" version="2.0.0" targetFramework="net45" /> + <package id="Microsoft.AspNet.Mvc" version="4.0.20710.0" targetFramework="net45" /> + <package id="Microsoft.AspNet.Razor" version="2.0.20715.0" targetFramework="net45" /> + <package id="Microsoft.AspNet.WebPages" version="2.0.20710.0" targetFramework="net45" /> + <package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net45" /> + <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" /> + <package id="NLog" version="2.0.0.2000" targetFramework="net40" /> + <package id="Validation" version="2.0.2.13022" targetFramework="net45" /> +</packages>
\ No newline at end of file |