diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index 33741706393..9f4b825a6ce 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -34,17 +34,6 @@ - - - - - - - - - - - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/BasicHtmlWebResponseObject.Common.cs similarity index 88% rename from src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs rename to src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/BasicHtmlWebResponseObject.Common.cs index 1dcb9f8575f..8bf89230e80 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/BasicHtmlWebResponseObject.Common.cs @@ -5,6 +5,7 @@ using System; using System.Management.Automation; using System.Net; +using System.Net.Http; using System.IO; using System.Text; using System.Text.RegularExpressions; @@ -16,7 +17,7 @@ namespace Microsoft.PowerShell.Commands /// /// Response object for html content without DOM parsing /// - public partial class BasicHtmlWebResponseObject : WebResponseObject + public class BasicHtmlWebResponseObject : WebResponseObject { #region Properties @@ -116,6 +117,31 @@ public WebCmdletElementCollection Images #endregion Properties + #region Constructors + + /// + /// Constructor for BasicHtmlWebResponseObject + /// + /// + public BasicHtmlWebResponseObject(HttpResponseMessage response) + : this(response, null) + { } + + /// + /// Constructor for HtmlWebResponseObject with memory stream + /// + /// + /// + public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream contentStream) + : base(response, contentStream) + { + EnsureHtmlParser(); + InitializeContent(); + InitializeRawContent(response); + } + + #endregion Constructors + #region Private Fields private static Regex s_tagRegex; @@ -242,6 +268,13 @@ protected void InitializeContent() } } + private void InitializeRawContent(HttpResponseMessage baseResponse) + { + StringBuilder raw = ContentHelper.GetRawContentHeader(baseResponse); + raw.Append(Content); + this.RawContent = raw.ToString(); + } + #endregion Methods } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs deleted file mode 100644 index 5e8055572a3..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs +++ /dev/null @@ -1,748 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.ObjectModel; -using System.Management.Automation; -using System.Net; -using System.IO; -using System.Text; -using System.Collections; -using System.Globalization; -using System.Security; -using System.Security.Authentication; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Microsoft.Win32; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// The valid values for the -Authentication parameter for Invoke-RestMethod and Invoke-WebRequest - /// - public enum WebAuthenticationType - { - /// - /// No authentication. Default. - /// - None, - - /// - /// RFC-7617 Basic Authentication. Requires -Credential - /// - Basic, - - /// - /// RFC-6750 OAuth 2.0 Bearer Authentication. Requires -Token - /// - Bearer, - - /// - /// RFC-6750 OAuth 2.0 Bearer Authentication. Requires -Token - /// - OAuth, - } - - // WebSslProtocol is used because not all SslProtocols are supported by HttpClientHandler. - // Also SslProtocols.Default is not the "default" for HttpClientHandler as SslProtocols.Ssl3 is not supported. - /// - /// The valid values for the -SslProtocol parameter for Invoke-RestMethod and Invoke-WebRequest - /// - [Flags] - public enum WebSslProtocol - { - /// - /// No SSL protocol will be set and the system defaults will be used. - /// - Default = 0, - - /// - /// Specifies the TLS 1.0 security protocol. The TLS protocol is defined in IETF RFC 2246. - /// - Tls = SslProtocols.Tls, - - /// - /// Specifies the TLS 1.1 security protocol. The TLS protocol is defined in IETF RFC 4346. - /// - Tls11 = SslProtocols.Tls11, - - /// - /// Specifies the TLS 1.2 security protocol. The TLS protocol is defined in IETF RFC 5246 - /// - Tls12 = SslProtocols.Tls12 - } - - /// - /// Base class for Invoke-RestMethod and Invoke-WebRequest commands. - /// - public abstract partial class WebRequestPSCmdlet : PSCmdlet - { - #region Virtual Properties - - #region URI - - /// - /// Deprecated. Gets or sets UseBasicParsing. This has no affect on the operation of the Cmdlet. - /// - [Parameter(DontShow = true)] - public virtual SwitchParameter UseBasicParsing { get; set; } = true; - - /// - /// gets or sets the Uri property - /// - [Parameter(Position = 0, Mandatory = true)] - [ValidateNotNullOrEmpty] - public virtual Uri Uri { get; set; } - - #endregion - - #region Session - /// - /// gets or sets the Session property - /// - [Parameter] - public virtual WebRequestSession WebSession { get; set; } - - /// - /// gets or sets the SessionVariable property - /// - [Parameter] - [Alias("SV")] - public virtual string SessionVariable { get; set; } - - #endregion - - #region Authorization and Credentials - - /// - /// Gets or sets the AllowUnencryptedAuthentication property - /// - [Parameter] - public virtual SwitchParameter AllowUnencryptedAuthentication { get; set; } - - /// - /// Gets or sets the Authentication property used to determin the Authentication method for the web session. - /// Authentication does not work with UseDefaultCredentials. - /// Authentication over unencrypted sessions requires AllowUnencryptedAuthentication. - /// Basic: Requires Credential - /// OAuth/Bearer: Requires Token - /// - [Parameter] - public virtual WebAuthenticationType Authentication { get; set; } = WebAuthenticationType.None; - - /// - /// gets or sets the Credential property - /// - [Parameter] - [Credential] - public virtual PSCredential Credential { get; set; } - - /// - /// gets or sets the UseDefaultCredentials property - /// - [Parameter] - public virtual SwitchParameter UseDefaultCredentials { get; set; } - - /// - /// gets or sets the CertificateThumbprint property - /// - [Parameter] - [ValidateNotNullOrEmpty] - public virtual string CertificateThumbprint { get; set; } - - /// - /// gets or sets the Certificate property - /// - [Parameter] - [ValidateNotNull] - public virtual X509Certificate Certificate { get; set; } - - /// - /// gets or sets the SkipCertificateCheck property - /// - [Parameter] - public virtual SwitchParameter SkipCertificateCheck { get; set; } - - /// - /// Gets or sets the TLS/SSL protocol used by the Web Cmdlet - /// - [Parameter] - public virtual WebSslProtocol SslProtocol { get; set; } = WebSslProtocol.Default; - - /// - /// Gets or sets the Token property. Token is required by Authentication OAuth and Bearer. - /// - [Parameter] - public virtual SecureString Token { get; set; } - - #endregion - - #region Headers - - /// - /// gets or sets the UserAgent property - /// - [Parameter] - public virtual string UserAgent { get; set; } - - /// - /// gets or sets the DisableKeepAlive property - /// - [Parameter] - public virtual SwitchParameter DisableKeepAlive { get; set; } - - /// - /// gets or sets the TimeOut property - /// - [Parameter] - [ValidateRange(0, Int32.MaxValue)] - public virtual int TimeoutSec { get; set; } - - /// - /// gets or sets the Headers property - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] - [Parameter] - public virtual IDictionary Headers { get; set; } - - #endregion - - #region Redirect - - /// - /// gets or sets the RedirectMax property - /// - [Parameter] - [ValidateRange(0, Int32.MaxValue)] - public virtual int MaximumRedirection - { - get { return _maximumRedirection; } - set { _maximumRedirection = value; } - } - private int _maximumRedirection = -1; - - #endregion - - #region Method - - /// - /// gets or sets the Method property - /// - [Parameter(ParameterSetName = "StandardMethod")] - [Parameter(ParameterSetName = "StandardMethodNoProxy")] - public virtual WebRequestMethod Method - { - get { return _method; } - set { _method = value; } - } - private WebRequestMethod _method = WebRequestMethod.Default; - - /// - /// gets or sets the CustomMethod property - /// - [Parameter(Mandatory=true,ParameterSetName = "CustomMethod")] - [Parameter(Mandatory=true,ParameterSetName = "CustomMethodNoProxy")] - [Alias("CM")] - [ValidateNotNullOrEmpty] - public virtual string CustomMethod - { - get { return _customMethod; } - set { _customMethod = value; } - } - private string _customMethod; - - #endregion - - #region NoProxy - - /// - /// gets or sets the NoProxy property - /// - [Parameter(Mandatory=true,ParameterSetName = "CustomMethodNoProxy")] - [Parameter(Mandatory=true,ParameterSetName = "StandardMethodNoProxy")] - public virtual SwitchParameter NoProxy { get; set; } - - #endregion - - #region Proxy - - /// - /// gets or sets the Proxy property - /// - [Parameter(ParameterSetName = "StandardMethod")] - [Parameter(ParameterSetName = "CustomMethod")] - public virtual Uri Proxy { get; set; } - - /// - /// gets or sets the ProxyCredential property - /// - [Parameter(ParameterSetName = "StandardMethod")] - [Parameter(ParameterSetName = "CustomMethod")] - [Credential] - public virtual PSCredential ProxyCredential { get; set; } - - /// - /// gets or sets the ProxyUseDefaultCredentials property - /// - [Parameter(ParameterSetName = "StandardMethod")] - [Parameter(ParameterSetName = "CustomMethod")] - public virtual SwitchParameter ProxyUseDefaultCredentials { get; set; } - - #endregion - - #region Input - - /// - /// gets or sets the Body property - /// - [Parameter(ValueFromPipeline = true)] - public virtual object Body { get; set; } - - /// - /// gets or sets the ContentType property - /// - [Parameter] - public virtual string ContentType { get; set; } - - /// - /// gets or sets the TransferEncoding property - /// - [Parameter] - [ValidateSet("chunked", "compress", "deflate", "gzip", "identity", IgnoreCase = true)] - public virtual string TransferEncoding { get; set; } - - /// - /// gets or sets the InFile property - /// - [Parameter] - public virtual string InFile { get; set; } - - /// - /// keep the original file path after the resolved provider path is - /// assigned to InFile - /// - private string _originalFilePath; - - #endregion - - #region Output - - /// - /// gets or sets the OutFile property - /// - [Parameter] - public virtual string OutFile { get; set; } - - /// - /// gets or sets the PassThrough property - /// - [Parameter] - public virtual SwitchParameter PassThru { get; set; } - - #endregion - - #endregion Virtual Properties - - #region Virtual Methods - - internal virtual void ValidateParameters() - { - // sessions - if ((null != WebSession) && (null != SessionVariable)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.SessionConflict, - "WebCmdletSessionConflictException"); - ThrowTerminatingError(error); - } - - // Authentication - if (UseDefaultCredentials && (Authentication != WebAuthenticationType.None)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationConflict, - "WebCmdletAuthenticationConflictException"); - ThrowTerminatingError(error); - } - if ((Authentication != WebAuthenticationType.None) && (null != Token) && (null != Credential)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenConflict, - "WebCmdletAuthenticationTokenConflictException"); - ThrowTerminatingError(error); - } - if ((Authentication == WebAuthenticationType.Basic) && (null == Credential)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationCredentialNotSupplied, - "WebCmdletAuthenticationCredentialNotSuppliedException"); - ThrowTerminatingError(error); - } - if ((Authentication == WebAuthenticationType.OAuth || Authentication == WebAuthenticationType.Bearer) && (null == Token)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenNotSupplied, - "WebCmdletAuthenticationTokenNotSuppliedException"); - ThrowTerminatingError(error); - } - if (!AllowUnencryptedAuthentication && (Authentication != WebAuthenticationType.None) && (Uri.Scheme != "https")) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.AllowUnencryptedAuthenticationRequired, - "WebCmdletAllowUnencryptedAuthenticationRequiredException"); - ThrowTerminatingError(error); - } - if (!AllowUnencryptedAuthentication && (null != Credential || UseDefaultCredentials) && (Uri.Scheme != "https")) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.AllowUnencryptedAuthenticationRequired, - "WebCmdletAllowUnencryptedAuthenticationRequiredException"); - ThrowTerminatingError(error); - } - - // credentials - if (UseDefaultCredentials && (null != Credential)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.CredentialConflict, - "WebCmdletCredentialConflictException"); - ThrowTerminatingError(error); - } - - // Proxy server - if (ProxyUseDefaultCredentials && (null != ProxyCredential)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.ProxyCredentialConflict, - "WebCmdletProxyCredentialConflictException"); - ThrowTerminatingError(error); - } - else if ((null == Proxy) && ((null != ProxyCredential) || ProxyUseDefaultCredentials)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.ProxyUriNotSupplied, - "WebCmdletProxyUriNotSuppliedException"); - ThrowTerminatingError(error); - } - - // request body content - if ((null != Body) && (null != InFile)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.BodyConflict, - "WebCmdletBodyConflictException"); - ThrowTerminatingError(error); - } - - // validate InFile path - if (InFile != null) - { - ProviderInfo provider = null; - ErrorRecord errorRecord = null; - - try - { - Collection providerPaths = GetResolvedProviderPathFromPSPath(InFile, out provider); - - if (!provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase)) - { - errorRecord = GetValidationError(WebCmdletStrings.NotFilesystemPath, - "WebCmdletInFileNotFilesystemPathException", InFile); - } - else - { - if (providerPaths.Count > 1) - { - errorRecord = GetValidationError(WebCmdletStrings.MultiplePathsResolved, - "WebCmdletInFileMultiplePathsResolvedException", InFile); - } - else if (providerPaths.Count == 0) - { - errorRecord = GetValidationError(WebCmdletStrings.NoPathResolved, - "WebCmdletInFileNoPathResolvedException", InFile); - } - else - { - if (Directory.Exists(providerPaths[0])) - { - errorRecord = GetValidationError(WebCmdletStrings.DirectoryPathSpecified, - "WebCmdletInFileNotFilePathException", InFile); - } - _originalFilePath = InFile; - InFile = providerPaths[0]; - } - } - } - catch (ItemNotFoundException pathNotFound) - { - errorRecord = new ErrorRecord(pathNotFound.ErrorRecord, pathNotFound); - } - catch (ProviderNotFoundException providerNotFound) - { - errorRecord = new ErrorRecord(providerNotFound.ErrorRecord, providerNotFound); - } - catch (System.Management.Automation.DriveNotFoundException driveNotFound) - { - errorRecord = new ErrorRecord(driveNotFound.ErrorRecord, driveNotFound); - } - - if (errorRecord != null) - { - ThrowTerminatingError(errorRecord); - } - } - - // output ?? - if (PassThru && (OutFile == null)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.OutFileMissing, - "WebCmdletOutFileMissingException"); - ThrowTerminatingError(error); - } - } - - internal virtual void PrepareSession() - { - // make sure we have a valid WebRequestSession object to work with - if (null == WebSession) - { - WebSession = new WebRequestSession(); - } - - if (null != SessionVariable) - { - // save the session back to the PS environment if requested - PSVariableIntrinsics vi = SessionState.PSVariable; - vi.Set(SessionVariable, WebSession); - } - - // - // handle credentials - // - if (null != Credential && Authentication == WebAuthenticationType.None) - { - // get the relevant NetworkCredential - NetworkCredential netCred = Credential.GetNetworkCredential(); - WebSession.Credentials = netCred; - - // supplying a credential overrides the UseDefaultCredentials setting - WebSession.UseDefaultCredentials = false; - } - else if ((null != Credential || null!= Token) && Authentication != WebAuthenticationType.None) - { - ProcessAuthentication(); - } - else if (UseDefaultCredentials) - { - WebSession.UseDefaultCredentials = true; - } - - - if (null != CertificateThumbprint) - { - X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser); - store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); - X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates; - X509Certificate2Collection tbCollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByThumbprint, CertificateThumbprint, false); - if (tbCollection.Count == 0) - { - CryptographicException ex = new CryptographicException(WebCmdletStrings.ThumbprintNotFound); - throw ex; - } - foreach (X509Certificate2 tbCert in tbCollection) - { - X509Certificate certificate = (X509Certificate)tbCert; - WebSession.AddCertificate(certificate); - } - } - - if (null != Certificate) - { - WebSession.AddCertificate(Certificate); - } - - // - // handle the user agent - // - if (null != UserAgent) - { - // store the UserAgent string - WebSession.UserAgent = UserAgent; - } - - if (null != Proxy) - { - WebProxy webProxy = new WebProxy(Proxy); - webProxy.BypassProxyOnLocal = false; - if (null != ProxyCredential) - { - webProxy.Credentials = ProxyCredential.GetNetworkCredential(); - } - else if (ProxyUseDefaultCredentials) - { - // If both ProxyCredential and ProxyUseDefaultCredentials are passed, - // UseDefaultCredentials will overwrite the supplied credentials. - webProxy.UseDefaultCredentials = true; - } - WebSession.Proxy = webProxy; - } - - if (-1 < MaximumRedirection) - { - WebSession.MaximumRedirection = MaximumRedirection; - } - - // store the other supplied headers - if (null != Headers) - { - foreach (string key in Headers.Keys) - { - // add the header value (or overwrite it if already present) - WebSession.Headers[key] = Headers[key].ToString(); - } - } - } - - #endregion Virtual Methods - - #region Helper Properties - - internal string QualifiedOutFile - { - get { return (QualifyFilePath(OutFile)); } - } - - internal bool ShouldSaveToOutFile - { - get { return (!string.IsNullOrEmpty(OutFile)); } - } - - internal bool ShouldWriteToPipeline - { - get { return (!ShouldSaveToOutFile || PassThru); } - } - - #endregion Helper Properties - - #region Helper Methods - - private Uri PrepareUri(Uri uri) - { - uri = CheckProtocol(uri); - - // before creating the web request, - // preprocess Body if content is a dictionary and method is GET (set as query) - IDictionary bodyAsDictionary; - LanguagePrimitives.TryConvertTo(Body, out bodyAsDictionary); - if ((null != bodyAsDictionary) - && ((IsStandardMethodSet() && (Method == WebRequestMethod.Default || Method == WebRequestMethod.Get)) - || (IsCustomMethodSet() && CustomMethod.ToUpperInvariant() == "GET"))) - { - UriBuilder uriBuilder = new UriBuilder(uri); - if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) - { - uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + FormatDictionary(bodyAsDictionary); - } - else - { - uriBuilder.Query = FormatDictionary(bodyAsDictionary); - } - uri = uriBuilder.Uri; - // set body to null to prevent later FillRequestStream - Body = null; - } - - return uri; - } - - private Uri CheckProtocol(Uri uri) - { - if (null == uri) { throw new ArgumentNullException("uri"); } - - if (!uri.IsAbsoluteUri) - { - uri = new Uri("http://" + uri.OriginalString); - } - return (uri); - } - - private string QualifyFilePath(string path) - { - string resolvedFilePath = PathUtils.ResolveFilePath(path, this, false); - return resolvedFilePath; - } - - private string FormatDictionary(IDictionary content) - { - if (content == null) - throw new ArgumentNullException("content"); - - StringBuilder bodyBuilder = new StringBuilder(); - foreach (string key in content.Keys) - { - if (0 < bodyBuilder.Length) - { - bodyBuilder.Append("&"); - } - - object value = content[key]; - - // URLEncode the key and value - string encodedKey = WebUtility.UrlEncode(key); - string encodedValue = String.Empty; - if (null != value) - { - encodedValue = WebUtility.UrlEncode(value.ToString()); - } - - bodyBuilder.AppendFormat("{0}={1}", encodedKey, encodedValue); - } - return bodyBuilder.ToString(); - } - - private ErrorRecord GetValidationError(string msg, string errorId) - { - var ex = new ValidationMetadataException(msg); - var error = new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); - return (error); - } - - private ErrorRecord GetValidationError(string msg, string errorId, params object[] args) - { - msg = string.Format(CultureInfo.InvariantCulture, msg, args); - var ex = new ValidationMetadataException(msg); - var error = new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); - return (error); - } - - private bool IsStandardMethodSet() - { - return (ParameterSetName == "StandardMethod"); - } - - private bool IsCustomMethodSet() - { - return (ParameterSetName == "CustomMethod"); - } - - private string GetBasicAuthorizationHeader() - { - string unencoded = String.Format("{0}:{1}", Credential.UserName, Credential.GetNetworkCredential().Password); - Byte[] bytes = Encoding.UTF8.GetBytes(unencoded); - return String.Format("Basic {0}", Convert.ToBase64String(bytes)); - } - - private string GetBearerAuthorizationHeader() - { - return String.Format("Bearer {0}", new NetworkCredential(String.Empty, Token).Password); - } - - private void ProcessAuthentication() - { - if(Authentication == WebAuthenticationType.Basic) - { - WebSession.Headers["Authorization"] = GetBasicAuthorizationHeader(); - } - else if (Authentication == WebAuthenticationType.Bearer || Authentication == WebAuthenticationType.OAuth) - { - WebSession.Headers["Authorization"] = GetBearerAuthorizationHeader(); - } - else - { - Diagnostics.Assert(false, String.Format("Unrecognized Authentication value: {0}", Authentication)); - } - } - - #endregion Helper Methods - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs deleted file mode 100644 index 3b9d1b211e1..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs +++ /dev/null @@ -1,99 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// WebResponseObject - /// - public partial class WebResponseObject - { - #region Properties - - /// - /// gets or protected sets the Content property - /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public byte[] Content { get; protected set; } - - /// - /// gets the StatusCode property - /// - public int StatusCode - { - get { return (WebResponseHelper.GetStatusCode(BaseResponse)); } - } - - /// - /// gets the StatusDescription property - /// - public string StatusDescription - { - get { return (WebResponseHelper.GetStatusDescription(BaseResponse)); } - } - - private MemoryStream _rawContentStream; - /// - /// gets the RawContentStream property - /// - public MemoryStream RawContentStream - { - get { return (_rawContentStream); } - } - - /// - /// gets the RawContentLength property - /// - public long RawContentLength - { - get { return (null == RawContentStream ? -1 : RawContentStream.Length); } - } - - /// - /// gets or protected sets the RawContent property - /// - public string RawContent { get; protected set; } - - #endregion Properties - - #region Methods - - /// - /// Reads the response content from the web response. - /// - private void InitializeContent() - { - this.Content = this.RawContentStream.ToArray(); - } - - private bool IsPrintable(char c) - { - return (Char.IsLetterOrDigit(c) || Char.IsPunctuation(c) || Char.IsSeparator(c) || Char.IsSymbol(c) || Char.IsWhiteSpace(c)); - } - - /// - /// Returns the string representation of this web response. - /// - /// The string representation of this web response. - public sealed override string ToString() - { - char[] stringContent = System.Text.Encoding.ASCII.GetChars(Content); - for (int counter = 0; counter < stringContent.Length; counter++) - { - if (!IsPrintable(stringContent[counter])) - { - stringContent[counter] = '.'; - } - } - - return new string(stringContent); - } - - #endregion Methods - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/ContentHelper.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ContentHelper.Common.cs similarity index 73% rename from src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/ContentHelper.Common.cs rename to src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ContentHelper.Common.cs index b7d9d0afc7a..aeca26eef61 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/ContentHelper.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ContentHelper.Common.cs @@ -3,13 +3,15 @@ --********************************************************************/ using System; +using System.Net.Http; +using System.Net.Http.Headers; using System.Management.Automation; using System.Text; using Microsoft.Win32; namespace Microsoft.PowerShell.Commands { - internal static partial class ContentHelper + internal static class ContentHelper { #region Constants @@ -23,6 +25,19 @@ internal static partial class ContentHelper #region Internal Methods + internal static Encoding GetEncoding(HttpResponseMessage response) + { + // ContentType may not exist in response header. + string charSet = response.Content.Headers.ContentType?.CharSet; + return GetEncodingOrDefault(charSet); + } + + internal static string GetContentType(HttpResponseMessage response) + { + // ContentType may not exist in response header. Return null if not. + return response.Content.Headers.ContentType?.MediaType; + } + internal static bool IsText(string contentType) { contentType = GetContentTypeSignature(contentType); @@ -65,6 +80,48 @@ internal static Encoding GetDefaultEncoding() return GetEncodingOrDefault((string)null); } + internal static StringBuilder GetRawContentHeader(HttpResponseMessage response) + { + StringBuilder raw = new StringBuilder(); + + string protocol = WebResponseHelper.GetProtocol(response); + if (!string.IsNullOrEmpty(protocol)) + { + int statusCode = WebResponseHelper.GetStatusCode(response); + string statusDescription = WebResponseHelper.GetStatusDescription(response); + raw.AppendFormat("{0} {1} {2}", protocol, statusCode, statusDescription); + raw.AppendLine(); + } + + HttpHeaders[] headerCollections = + { + response.Headers, + response.Content == null ? null : response.Content.Headers + }; + + foreach (var headerCollection in headerCollections) + { + if (headerCollection == null) + { + continue; + } + foreach (var header in headerCollection) + { + // Headers may have multiple entries with different values + foreach (var headerValue in header.Value) + { + raw.Append(header.Key); + raw.Append(": "); + raw.Append(headerValue); + raw.AppendLine(); + } + } + } + + raw.AppendLine(); + return raw; + } + #endregion Internal Methods #region Private Helper Methods diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/BasicHtmlWebResponseObject.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/BasicHtmlWebResponseObject.CoreClr.cs deleted file mode 100644 index 1a8436b1255..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/BasicHtmlWebResponseObject.CoreClr.cs +++ /dev/null @@ -1,52 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Net.Http; -using System.IO; -using System.Text; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// Response object for html content without DOM parsing - /// - public partial class BasicHtmlWebResponseObject : WebResponseObject - { - #region Constructors - - /// - /// Constructor for BasicHtmlWebResponseObject - /// - /// - public BasicHtmlWebResponseObject(HttpResponseMessage response) - : this(response, null) - { } - - /// - /// Constructor for HtmlWebResponseObject with memory stream - /// - /// - /// - public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream contentStream) - : base(response, contentStream) - { - EnsureHtmlParser(); - InitializeContent(); - InitializeRawContent(response); - } - - #endregion Constructors - - #region Methods - - private void InitializeRawContent(HttpResponseMessage baseResponse) - { - StringBuilder raw = ContentHelper.GetRawContentHeader(baseResponse); - raw.Append(Content); - this.RawContent = raw.ToString(); - } - - #endregion Methods - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/ContentHelper.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/ContentHelper.CoreClr.cs deleted file mode 100644 index c10ee36b001..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/ContentHelper.CoreClr.cs +++ /dev/null @@ -1,69 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Text; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; - -namespace Microsoft.PowerShell.Commands -{ - internal static partial class ContentHelper - { - internal static Encoding GetEncoding(HttpResponseMessage response) - { - // ContentType may not exist in response header. - string charSet = response.Content.Headers.ContentType?.CharSet; - return GetEncodingOrDefault(charSet); - } - - internal static string GetContentType(HttpResponseMessage response) - { - // ContentType may not exist in response header. Return null if not. - return response.Content.Headers.ContentType?.MediaType; - } - - internal static StringBuilder GetRawContentHeader(HttpResponseMessage response) - { - StringBuilder raw = new StringBuilder(); - - string protocol = WebResponseHelper.GetProtocol(response); - if (!string.IsNullOrEmpty(protocol)) - { - int statusCode = WebResponseHelper.GetStatusCode(response); - string statusDescription = WebResponseHelper.GetStatusDescription(response); - raw.AppendFormat("{0} {1} {2}", protocol, statusCode, statusDescription); - raw.AppendLine(); - } - - HttpHeaders[] headerCollections = - { - response.Headers, - response.Content == null ? null : response.Content.Headers - }; - - foreach (var headerCollection in headerCollections) - { - if (headerCollection == null) - { - continue; - } - foreach (var header in headerCollection) - { - // Headers may have multiple entries with different values - foreach (var headerValue in header.Value) - { - raw.Append(header.Key); - raw.Append(": "); - raw.Append(headerValue); - raw.AppendLine(); - } - } - } - - raw.AppendLine(); - return raw; - } - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeRestMethodCommand.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeRestMethodCommand.CoreClr.cs deleted file mode 100644 index eabb9fc0183..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeRestMethodCommand.CoreClr.cs +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Management.Automation; -using System.Net.Http; -using System.Text; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// The Invoke-RestMethod command - /// This command makes an HTTP or HTTPS request to a web service, - /// and returns the response in an appropriate way. - /// Intended to work against the wide spectrum of "RESTful" web services - /// currently deployed across the web. - /// - [Cmdlet(VerbsLifecycle.Invoke, "RestMethod", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217034", DefaultParameterSetName = "StandardMethod")] - public partial class InvokeRestMethodCommand : WebRequestPSCmdlet - { - #region Virtual Method Overrides - - /// - /// Process the web response and output corresponding objects. - /// - /// - internal override void ProcessResponse(HttpResponseMessage response) - { - if (null == response) { throw new ArgumentNullException("response"); } - - using (BufferingStreamReader responseStream = new BufferingStreamReader(StreamHelper.GetResponseStream(response))) - { - if (ShouldWriteToPipeline) - { - // First see if it is an RSS / ATOM feed, in which case we can - // stream it - unless the user has overridden it with a return type of "XML" - if (TryProcessFeedStream(responseStream)) - { - // Do nothing, content has been processed. - } - else - { - // determine the response type - RestReturnType returnType = CheckReturnType(response); - - // Try to get the response encoding from the ContentType header. - Encoding encoding = null; - string charSet = response.Content.Headers.ContentType?.CharSet; - if (!string.IsNullOrEmpty(charSet)) - { - // NOTE: Don't use ContentHelper.GetEncoding; it returns a - // default which bypasses checking for a meta charset value. - StreamHelper.TryGetEncoding(charSet, out encoding); - } - - object obj = null; - Exception ex = null; - - string str = StreamHelper.DecodeStream(responseStream, ref encoding); - // NOTE: Tests use this verbose output to verify the encoding. - WriteVerbose(string.Format - ( - System.Globalization.CultureInfo.InvariantCulture, - "Content encoding: {0}", - string.IsNullOrEmpty(encoding.HeaderName) ? encoding.EncodingName : encoding.HeaderName) - ); - bool convertSuccess = false; - - if (returnType == RestReturnType.Json) - { - convertSuccess = TryConvertToJson(str, out obj, ref ex) || TryConvertToXml(str, out obj, ref ex); - } - // default to try xml first since it's more common - else - { - convertSuccess = TryConvertToXml(str, out obj, ref ex) || TryConvertToJson(str, out obj, ref ex); - } - - if (!convertSuccess) - { - // fallback to string - obj = str; - } - - WriteObject(obj); - } - } - - if (ShouldSaveToOutFile) - { - StreamHelper.SaveStreamToFile(responseStream, QualifiedOutFile, this); - } - - if (!String.IsNullOrEmpty(ResponseHeadersVariable)) - { - PSVariableIntrinsics vi = SessionState.PSVariable; - vi.Set(ResponseHeadersVariable, WebResponseHelper.GetHeadersDictionary(response)); - } - } - } - - #endregion Virtual Method Overrides - - #region Helper Methods - - private RestReturnType CheckReturnType(HttpResponseMessage response) - { - if (null == response) { throw new ArgumentNullException("response"); } - - RestReturnType rt = RestReturnType.Detect; - string contentType = ContentHelper.GetContentType(response); - if (string.IsNullOrEmpty(contentType)) - { - rt = RestReturnType.Detect; - } - else if (ContentHelper.IsJson(contentType)) - { - rt = RestReturnType.Json; - } - else if (ContentHelper.IsXml(contentType)) - { - rt = RestReturnType.Xml; - } - - return (rt); - } - - #endregion Helper Methods - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/HttpKnownHeaderNames.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/HttpKnownHeaderNames.cs similarity index 98% rename from src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/HttpKnownHeaderNames.cs rename to src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/HttpKnownHeaderNames.cs index 944df03eb4e..0c64e02f8bf 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/HttpKnownHeaderNames.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/HttpKnownHeaderNames.cs @@ -7,7 +7,7 @@ namespace Microsoft.PowerShell.Commands { - internal static partial class HttpKnownHeaderNames + internal static class HttpKnownHeaderNames { #region Known_HTTP_Header_Names diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/InvokeRestMethodCommand.Common.cs similarity index 69% rename from src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs rename to src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/InvokeRestMethodCommand.Common.cs index a755b1d4d0b..e330ea311a6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/InvokeRestMethodCommand.Common.cs @@ -3,15 +3,25 @@ --********************************************************************/ using System; -using System.Management.Automation; using System.IO; +using System.Net.Http; +using System.Management.Automation; +using System.Text; using System.Xml; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Microsoft.PowerShell.Commands { - public partial class InvokeRestMethodCommand + /// + /// The Invoke-RestMethod command + /// This command makes an HTTP or HTTPS request to a web service, + /// and returns the response in an appropriate way. + /// Intended to work against the wide spectrum of "RESTful" web services + /// currently deployed across the web. + /// + [Cmdlet(VerbsLifecycle.Invoke, "RestMethod", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217034", DefaultParameterSetName = "StandardMethod")] + public class InvokeRestMethodCommand : WebRequestPSCmdlet { #region Parameters @@ -71,8 +81,114 @@ public int MaximumFollowRelLink #endregion Parameters + #region Virtual Method Overrides + + /// + /// Process the web response and output corresponding objects. + /// + /// + internal override void ProcessResponse(HttpResponseMessage response) + { + if (null == response) { throw new ArgumentNullException("response"); } + + using (BufferingStreamReader responseStream = new BufferingStreamReader(StreamHelper.GetResponseStream(response))) + { + if (ShouldWriteToPipeline) + { + // First see if it is an RSS / ATOM feed, in which case we can + // stream it - unless the user has overridden it with a return type of "XML" + if (TryProcessFeedStream(responseStream)) + { + // Do nothing, content has been processed. + } + else + { + // determine the response type + RestReturnType returnType = CheckReturnType(response); + + // Try to get the response encoding from the ContentType header. + Encoding encoding = null; + string charSet = response.Content.Headers.ContentType?.CharSet; + if (!string.IsNullOrEmpty(charSet)) + { + // NOTE: Don't use ContentHelper.GetEncoding; it returns a + // default which bypasses checking for a meta charset value. + StreamHelper.TryGetEncoding(charSet, out encoding); + } + + object obj = null; + Exception ex = null; + + string str = StreamHelper.DecodeStream(responseStream, ref encoding); + // NOTE: Tests use this verbose output to verify the encoding. + WriteVerbose(string.Format + ( + System.Globalization.CultureInfo.InvariantCulture, + "Content encoding: {0}", + string.IsNullOrEmpty(encoding.HeaderName) ? encoding.EncodingName : encoding.HeaderName) + ); + bool convertSuccess = false; + + if (returnType == RestReturnType.Json) + { + convertSuccess = TryConvertToJson(str, out obj, ref ex) || TryConvertToXml(str, out obj, ref ex); + } + // default to try xml first since it's more common + else + { + convertSuccess = TryConvertToXml(str, out obj, ref ex) || TryConvertToJson(str, out obj, ref ex); + } + + if (!convertSuccess) + { + // fallback to string + obj = str; + } + + WriteObject(obj); + } + } + + if (ShouldSaveToOutFile) + { + StreamHelper.SaveStreamToFile(responseStream, QualifiedOutFile, this); + } + + if (!String.IsNullOrEmpty(ResponseHeadersVariable)) + { + PSVariableIntrinsics vi = SessionState.PSVariable; + vi.Set(ResponseHeadersVariable, WebResponseHelper.GetHeadersDictionary(response)); + } + } + } + + #endregion Virtual Method Overrides + + #region Helper Methods + private RestReturnType CheckReturnType(HttpResponseMessage response) + { + if (null == response) { throw new ArgumentNullException("response"); } + + RestReturnType rt = RestReturnType.Detect; + string contentType = ContentHelper.GetContentType(response); + if (string.IsNullOrEmpty(contentType)) + { + rt = RestReturnType.Detect; + } + else if (ContentHelper.IsJson(contentType)) + { + rt = RestReturnType.Json; + } + else if (ContentHelper.IsXml(contentType)) + { + rt = RestReturnType.Xml; + } + + return (rt); + } + private bool TryProcessFeedStream(BufferingStreamReader responseStream) { bool isRssOrFeed = false; @@ -343,4 +459,4 @@ public override void Write(byte[] buffer, int offset, int count) } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeWebRequestCommand.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/InvokeWebRequestCommand.CoreClr.cs similarity index 100% rename from src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeWebRequestCommand.CoreClr.cs rename to src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/InvokeWebRequestCommand.CoreClr.cs diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebProxy.cs similarity index 100% rename from src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs rename to src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebProxy.cs diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestPSCmdlet.Common.cs similarity index 57% rename from src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs rename to src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestPSCmdlet.Common.cs index 0163b5f0958..7fa6a9f6987 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestPSCmdlet.Common.cs @@ -1,26 +1,31 @@ -/********************************************************************++ +/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; +using System.Linq; using System.Management.Automation; using System.Net; using System.Net.Http; using System.Net.Http.Headers; -using System.IO; -using System.Text; -using System.Collections; -using System.Globalization; +using System.Security; using System.Security.Authentication; using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Xml; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using System.Linq; +using Microsoft.Win32; namespace Microsoft.PowerShell.Commands { + /// /// Exception class for webcmdlets to enable returning HTTP error response /// @@ -42,11 +47,146 @@ public HttpResponseException (string message, HttpResponseMessage response) : ba public HttpResponseMessage Response { get; private set; } } + /// + /// The valid values for the -Authentication parameter for Invoke-RestMethod and Invoke-WebRequest + /// + public enum WebAuthenticationType + { + /// + /// No authentication. Default. + /// + None, + + /// + /// RFC-7617 Basic Authentication. Requires -Credential + /// + Basic, + + /// + /// RFC-6750 OAuth 2.0 Bearer Authentication. Requires -Token + /// + Bearer, + + /// + /// RFC-6750 OAuth 2.0 Bearer Authentication. Requires -Token + /// + OAuth, + } + + // WebSslProtocol is used because not all SslProtocols are supported by HttpClientHandler. + // Also SslProtocols.Default is not the "default" for HttpClientHandler as SslProtocols.Ssl3 is not supported. + /// + /// The valid values for the -SslProtocol parameter for Invoke-RestMethod and Invoke-WebRequest + /// + [Flags] + public enum WebSslProtocol + { + /// + /// No SSL protocol will be set and the system defaults will be used. + /// + Default = 0, + + /// + /// Specifies the TLS 1.0 security protocol. The TLS protocol is defined in IETF RFC 2246. + /// + Tls = SslProtocols.Tls, + + /// + /// Specifies the TLS 1.1 security protocol. The TLS protocol is defined in IETF RFC 4346. + /// + Tls11 = SslProtocols.Tls11, + + /// + /// Specifies the TLS 1.2 security protocol. The TLS protocol is defined in IETF RFC 5246 + /// + Tls12 = SslProtocols.Tls12 + } + /// /// Base class for Invoke-RestMethod and Invoke-WebRequest commands. /// - public abstract partial class WebRequestPSCmdlet : PSCmdlet + public abstract class WebRequestPSCmdlet : PSCmdlet { + + #region Virtual Properties + + #region URI + + /// + /// Deprecated. Gets or sets UseBasicParsing. This has no affect on the operation of the Cmdlet. + /// + [Parameter(DontShow = true)] + public virtual SwitchParameter UseBasicParsing { get; set; } = true; + + /// + /// gets or sets the Uri property + /// + [Parameter(Position = 0, Mandatory = true)] + [ValidateNotNullOrEmpty] + public virtual Uri Uri { get; set; } + + #endregion + + #region Session + /// + /// gets or sets the Session property + /// + [Parameter] + public virtual WebRequestSession WebSession { get; set; } + + /// + /// gets or sets the SessionVariable property + /// + [Parameter] + [Alias("SV")] + public virtual string SessionVariable { get; set; } + + #endregion + + #region Authorization and Credentials + + /// + /// Gets or sets the AllowUnencryptedAuthentication property + /// + [Parameter] + public virtual SwitchParameter AllowUnencryptedAuthentication { get; set; } + + /// + /// Gets or sets the Authentication property used to determin the Authentication method for the web session. + /// Authentication does not work with UseDefaultCredentials. + /// Authentication over unencrypted sessions requires AllowUnencryptedAuthentication. + /// Basic: Requires Credential + /// OAuth/Bearer: Requires Token + /// + [Parameter] + public virtual WebAuthenticationType Authentication { get; set; } = WebAuthenticationType.None; + + /// + /// gets or sets the Credential property + /// + [Parameter] + [Credential] + public virtual PSCredential Credential { get; set; } + + /// + /// gets or sets the UseDefaultCredentials property + /// + [Parameter] + public virtual SwitchParameter UseDefaultCredentials { get; set; } + + /// + /// gets or sets the CertificateThumbprint property + /// + [Parameter] + [ValidateNotNullOrEmpty] + public virtual string CertificateThumbprint { get; set; } + + /// + /// gets or sets the Certificate property + /// + [Parameter] + [ValidateNotNull] + public virtual X509Certificate Certificate { get; set; } /// /// gets or sets the PreserveAuthorizationOnRedirect property @@ -63,6 +203,54 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet [Parameter] public virtual SwitchParameter PreserveAuthorizationOnRedirect { get; set; } + /// + /// gets or sets the SkipCertificateCheck property + /// + [Parameter] + public virtual SwitchParameter SkipCertificateCheck { get; set; } + + /// + /// Gets or sets the TLS/SSL protocol used by the Web Cmdlet + /// + [Parameter] + public virtual WebSslProtocol SslProtocol { get; set; } = WebSslProtocol.Default; + + /// + /// Gets or sets the Token property. Token is required by Authentication OAuth and Bearer. + /// + [Parameter] + public virtual SecureString Token { get; set; } + + #endregion + + #region Headers + + /// + /// gets or sets the UserAgent property + /// + [Parameter] + public virtual string UserAgent { get; set; } + + /// + /// gets or sets the DisableKeepAlive property + /// + [Parameter] + public virtual SwitchParameter DisableKeepAlive { get; set; } + + /// + /// gets or sets the TimeOut property + /// + [Parameter] + [ValidateRange(0, Int32.MaxValue)] + public virtual int TimeoutSec { get; set; } + + /// + /// gets or sets the Headers property + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + [Parameter] + public virtual IDictionary Headers { get; set; } + /// /// gets or sets the SkipHeaderValidation property /// @@ -72,16 +260,145 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet [Parameter] public virtual SwitchParameter SkipHeaderValidation { get; set; } - #region Abstract Methods + #endregion + + #region Redirect /// - /// Read the supplied WebResponse object and push the - /// resulting output into the pipeline. + /// gets or sets the RedirectMax property /// - /// Instance of a WebResponse object to be processed - internal abstract void ProcessResponse(HttpResponseMessage response); + [Parameter] + [ValidateRange(0, Int32.MaxValue)] + public virtual int MaximumRedirection + { + get { return _maximumRedirection; } + set { _maximumRedirection = value; } + } + private int _maximumRedirection = -1; - #endregion Abstract Methods + #endregion + + #region Method + + /// + /// gets or sets the Method property + /// + [Parameter(ParameterSetName = "StandardMethod")] + [Parameter(ParameterSetName = "StandardMethodNoProxy")] + public virtual WebRequestMethod Method + { + get { return _method; } + set { _method = value; } + } + private WebRequestMethod _method = WebRequestMethod.Default; + + /// + /// gets or sets the CustomMethod property + /// + [Parameter(Mandatory=true,ParameterSetName = "CustomMethod")] + [Parameter(Mandatory=true,ParameterSetName = "CustomMethodNoProxy")] + [Alias("CM")] + [ValidateNotNullOrEmpty] + public virtual string CustomMethod + { + get { return _customMethod; } + set { _customMethod = value; } + } + private string _customMethod; + + #endregion + + #region NoProxy + + /// + /// gets or sets the NoProxy property + /// + [Parameter(Mandatory=true,ParameterSetName = "CustomMethodNoProxy")] + [Parameter(Mandatory=true,ParameterSetName = "StandardMethodNoProxy")] + public virtual SwitchParameter NoProxy { get; set; } + + #endregion + + #region Proxy + + /// + /// gets or sets the Proxy property + /// + [Parameter(ParameterSetName = "StandardMethod")] + [Parameter(ParameterSetName = "CustomMethod")] + public virtual Uri Proxy { get; set; } + + /// + /// gets or sets the ProxyCredential property + /// + [Parameter(ParameterSetName = "StandardMethod")] + [Parameter(ParameterSetName = "CustomMethod")] + [Credential] + public virtual PSCredential ProxyCredential { get; set; } + + /// + /// gets or sets the ProxyUseDefaultCredentials property + /// + [Parameter(ParameterSetName = "StandardMethod")] + [Parameter(ParameterSetName = "CustomMethod")] + public virtual SwitchParameter ProxyUseDefaultCredentials { get; set; } + + #endregion + + #region Input + + /// + /// gets or sets the Body property + /// + [Parameter(ValueFromPipeline = true)] + public virtual object Body { get; set; } + + /// + /// gets or sets the ContentType property + /// + [Parameter] + public virtual string ContentType { get; set; } + + /// + /// gets or sets the TransferEncoding property + /// + [Parameter] + [ValidateSet("chunked", "compress", "deflate", "gzip", "identity", IgnoreCase = true)] + public virtual string TransferEncoding { get; set; } + + /// + /// gets or sets the InFile property + /// + [Parameter] + public virtual string InFile { get; set; } + + /// + /// keep the original file path after the resolved provider path is + /// assigned to InFile + /// + private string _originalFilePath; + + #endregion + + #region Output + + /// + /// gets or sets the OutFile property + /// + [Parameter] + public virtual string OutFile { get; set; } + + /// + /// gets or sets the PassThrough property + /// + [Parameter] + public virtual SwitchParameter PassThru { get; set; } + + #endregion + + #endregion Virtual Properties + + #region Private Properties /// /// Cancellation token source @@ -108,32 +425,104 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet /// internal int _maximumFollowRelLink = Int32.MaxValue; - private HttpMethod GetHttpMethod(WebRequestMethod method) + #endregion + + #region Virtual Methods + + internal virtual void FillRequestStream(HttpRequestMessage request) { - switch (Method) + if (null == request) { throw new ArgumentNullException("request"); } + + // set the content type + if (ContentType != null) { - case WebRequestMethod.Default: - case WebRequestMethod.Get: - return HttpMethod.Get; - case WebRequestMethod.Head: - return HttpMethod.Head; - case WebRequestMethod.Post: - return HttpMethod.Post; - case WebRequestMethod.Put: - return HttpMethod.Put; - case WebRequestMethod.Delete: - return HttpMethod.Delete; - case WebRequestMethod.Trace: - return HttpMethod.Trace; - case WebRequestMethod.Options: - return HttpMethod.Options; - default: - // Merge and Patch - return new HttpMethod(Method.ToString().ToUpperInvariant()); + WebSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = ContentType; + //request + } + // ContentType == null + else if (Method == WebRequestMethod.Post || (IsCustomMethodSet() && CustomMethod.ToUpperInvariant() == "POST")) + { + // Win8:545310 Invoke-WebRequest does not properly set MIME type for POST + string contentType = null; + WebSession.ContentHeaders.TryGetValue(HttpKnownHeaderNames.ContentType, out contentType); + if (string.IsNullOrEmpty(contentType)) + { + WebSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = "application/x-www-form-urlencoded"; + } } - } - #region Virtual Methods + // coerce body into a usable form + if (Body != null) + { + object content = Body; + + // make sure we're using the base object of the body, not the PSObject wrapper + PSObject psBody = Body as PSObject; + if (psBody != null) + { + content = psBody.BaseObject; + } + + if (content is FormObject) + { + FormObject form = content as FormObject; + SetRequestContent(request, form.Fields); + } + else if (content is IDictionary && request.Method != HttpMethod.Get) + { + IDictionary dictionary = content as IDictionary; + SetRequestContent(request, dictionary); + } + else if (content is XmlNode) + { + XmlNode xmlNode = content as XmlNode; + SetRequestContent(request, xmlNode); + } + else if (content is Stream) + { + Stream stream = content as Stream; + SetRequestContent(request, stream); + } + else if (content is byte[]) + { + byte[] bytes = content as byte[]; + SetRequestContent(request, bytes); + } + else if (content is MultipartFormDataContent multipartFormDataContent) + { + WebSession.ContentHeaders.Clear(); + SetRequestContent(request, multipartFormDataContent); + } + else + { + SetRequestContent(request, + (string)LanguagePrimitives.ConvertTo(content, typeof(string), CultureInfo.InvariantCulture)); + } + } + else if (InFile != null) // copy InFile data + { + try + { + // open the input file + SetRequestContent(request, new FileStream(InFile, FileMode.Open)); + } + catch (UnauthorizedAccessException) + { + string msg = string.Format(CultureInfo.InvariantCulture, WebCmdletStrings.AccessDenied, + _originalFilePath); + throw new UnauthorizedAccessException(msg); + } + } + + // Add the content headers + if (request.Content != null) + { + foreach (var entry in WebSession.ContentHeaders) + { + request.Content.Headers.Add(entry.Key, entry.Value); + } + } + } // NOTE: Only pass true for handleRedirect if the original request has an authorization header // and PreserveAuthorizationOnRedirect is NOT set. @@ -319,99 +708,43 @@ internal virtual HttpRequestMessage GetRequest(Uri uri, bool stripAuthorization) return (request); } - internal virtual void FillRequestStream(HttpRequestMessage request) + internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestMessage request, bool stripAuthorization) { - if (null == request) { throw new ArgumentNullException("request"); } + if (client == null) { throw new ArgumentNullException("client"); } + if (request == null) { throw new ArgumentNullException("request"); } - // set the content type - if (ContentType != null) - { - WebSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = ContentType; - //request - } - // ContentType == null - else if (Method == WebRequestMethod.Post || (IsCustomMethodSet() && CustomMethod.ToUpperInvariant() == "POST")) - { - // Win8:545310 Invoke-WebRequest does not properly set MIME type for POST - string contentType = null; - WebSession.ContentHeaders.TryGetValue(HttpKnownHeaderNames.ContentType, out contentType); - if (string.IsNullOrEmpty(contentType)) - { - WebSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = "application/x-www-form-urlencoded"; - } - } + _cancelToken = new CancellationTokenSource(); + HttpResponseMessage response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); - // coerce body into a usable form - if (Body != null) + if (stripAuthorization && IsRedirectCode(response.StatusCode)) { - object content = Body; - - // make sure we're using the base object of the body, not the PSObject wrapper - PSObject psBody = Body as PSObject; - if (psBody != null) - { - content = psBody.BaseObject; - } + _cancelToken.Cancel(); + _cancelToken = null; - if (content is FormObject) - { - FormObject form = content as FormObject; - SetRequestContent(request, form.Fields); - } - else if (content is IDictionary && request.Method != HttpMethod.Get) - { - IDictionary dictionary = content as IDictionary; - SetRequestContent(request, dictionary); - } - else if (content is XmlNode) - { - XmlNode xmlNode = content as XmlNode; - SetRequestContent(request, xmlNode); - } - else if (content is Stream) - { - Stream stream = content as Stream; - SetRequestContent(request, stream); - } - else if (content is byte[]) - { - byte[] bytes = content as byte[]; - SetRequestContent(request, bytes); - } - else if (content is MultipartFormDataContent multipartFormDataContent) - { - WebSession.ContentHeaders.Clear(); - SetRequestContent(request, multipartFormDataContent); - } - else - { - SetRequestContent(request, - (string)LanguagePrimitives.ConvertTo(content, typeof(string), CultureInfo.InvariantCulture)); - } - } - else if (InFile != null) // copy InFile data - { - try - { - // open the input file - SetRequestContent(request, new FileStream(InFile, FileMode.Open)); + // if explicit count was provided, reduce it for this redirection. + if (WebSession.MaximumRedirection > 0) + { + WebSession.MaximumRedirection--; } - catch (UnauthorizedAccessException) + // For selected redirects that used POST, GET must be used with the + // redirected Location. + // Since GET is the default; POST only occurs when -Method POST is used. + if (Method == WebRequestMethod.Post && IsRedirectToGet(response.StatusCode)) { - string msg = string.Format(CultureInfo.InvariantCulture, WebCmdletStrings.AccessDenied, - _originalFilePath); - throw new UnauthorizedAccessException(msg); + // See https://msdn.microsoft.com/en-us/library/system.net.httpstatuscode(v=vs.110).aspx + Method = WebRequestMethod.Get; } - } - // Add the content headers - if (request.Content != null) - { - foreach (var entry in WebSession.ContentHeaders) + // recreate the HttpClient with redirection enabled since the first call suppressed redirection + using (client = GetHttpClient(false)) + using (HttpRequestMessage redirectRequest = GetRequest(response.Headers.Location, stripAuthorization:true)) { - request.Content.Headers.Add(entry.Key, entry.Value); + FillRequestStream(redirectRequest); + _cancelToken = new CancellationTokenSource(); + response = client.SendAsync(redirectRequest, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); } } + return response; } // Returns true if the status code is one of the supported redirection codes. @@ -448,52 +781,290 @@ static bool IsRedirectToGet(HttpStatusCode code) ); } - internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestMessage request, bool stripAuthorization) + internal virtual void UpdateSession(HttpResponseMessage response) { - if (client == null) { throw new ArgumentNullException("client"); } - if (request == null) { throw new ArgumentNullException("request"); } + if (response == null) { throw new ArgumentNullException("response"); } + } - _cancelToken = new CancellationTokenSource(); - HttpResponseMessage response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); + internal virtual void ValidateParameters() + { + // sessions + if ((null != WebSession) && (null != SessionVariable)) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.SessionConflict, + "WebCmdletSessionConflictException"); + ThrowTerminatingError(error); + } - if (stripAuthorization && IsRedirectCode(response.StatusCode)) + // Authentication + if (UseDefaultCredentials && (Authentication != WebAuthenticationType.None)) { - _cancelToken.Cancel(); - _cancelToken = null; + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationConflict, + "WebCmdletAuthenticationConflictException"); + ThrowTerminatingError(error); + } + if ((Authentication != WebAuthenticationType.None) && (null != Token) && (null != Credential)) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenConflict, + "WebCmdletAuthenticationTokenConflictException"); + ThrowTerminatingError(error); + } + if ((Authentication == WebAuthenticationType.Basic) && (null == Credential)) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationCredentialNotSupplied, + "WebCmdletAuthenticationCredentialNotSuppliedException"); + ThrowTerminatingError(error); + } + if ((Authentication == WebAuthenticationType.OAuth || Authentication == WebAuthenticationType.Bearer) && (null == Token)) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenNotSupplied, + "WebCmdletAuthenticationTokenNotSuppliedException"); + ThrowTerminatingError(error); + } + if (!AllowUnencryptedAuthentication && (Authentication != WebAuthenticationType.None) && (Uri.Scheme != "https")) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.AllowUnencryptedAuthenticationRequired, + "WebCmdletAllowUnencryptedAuthenticationRequiredException"); + ThrowTerminatingError(error); + } + if (!AllowUnencryptedAuthentication && (null != Credential || UseDefaultCredentials) && (Uri.Scheme != "https")) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.AllowUnencryptedAuthenticationRequired, + "WebCmdletAllowUnencryptedAuthenticationRequiredException"); + ThrowTerminatingError(error); + } - // if explicit count was provided, reduce it for this redirection. - if (WebSession.MaximumRedirection > 0) + // credentials + if (UseDefaultCredentials && (null != Credential)) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.CredentialConflict, + "WebCmdletCredentialConflictException"); + ThrowTerminatingError(error); + } + + // Proxy server + if (ProxyUseDefaultCredentials && (null != ProxyCredential)) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.ProxyCredentialConflict, + "WebCmdletProxyCredentialConflictException"); + ThrowTerminatingError(error); + } + else if ((null == Proxy) && ((null != ProxyCredential) || ProxyUseDefaultCredentials)) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.ProxyUriNotSupplied, + "WebCmdletProxyUriNotSuppliedException"); + ThrowTerminatingError(error); + } + + // request body content + if ((null != Body) && (null != InFile)) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.BodyConflict, + "WebCmdletBodyConflictException"); + ThrowTerminatingError(error); + } + + // validate InFile path + if (InFile != null) + { + ProviderInfo provider = null; + ErrorRecord errorRecord = null; + + try { - WebSession.MaximumRedirection--; + Collection providerPaths = GetResolvedProviderPathFromPSPath(InFile, out provider); + + if (!provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase)) + { + errorRecord = GetValidationError(WebCmdletStrings.NotFilesystemPath, + "WebCmdletInFileNotFilesystemPathException", InFile); + } + else + { + if (providerPaths.Count > 1) + { + errorRecord = GetValidationError(WebCmdletStrings.MultiplePathsResolved, + "WebCmdletInFileMultiplePathsResolvedException", InFile); + } + else if (providerPaths.Count == 0) + { + errorRecord = GetValidationError(WebCmdletStrings.NoPathResolved, + "WebCmdletInFileNoPathResolvedException", InFile); + } + else + { + if (Directory.Exists(providerPaths[0])) + { + errorRecord = GetValidationError(WebCmdletStrings.DirectoryPathSpecified, + "WebCmdletInFileNotFilePathException", InFile); + } + _originalFilePath = InFile; + InFile = providerPaths[0]; + } + } } - // For selected redirects that used POST, GET must be used with the - // redirected Location. - // Since GET is the default; POST only occurs when -Method POST is used. - if (Method == WebRequestMethod.Post && IsRedirectToGet(response.StatusCode)) + catch (ItemNotFoundException pathNotFound) { - // See https://msdn.microsoft.com/en-us/library/system.net.httpstatuscode(v=vs.110).aspx - Method = WebRequestMethod.Get; + errorRecord = new ErrorRecord(pathNotFound.ErrorRecord, pathNotFound); + } + catch (ProviderNotFoundException providerNotFound) + { + errorRecord = new ErrorRecord(providerNotFound.ErrorRecord, providerNotFound); + } + catch (System.Management.Automation.DriveNotFoundException driveNotFound) + { + errorRecord = new ErrorRecord(driveNotFound.ErrorRecord, driveNotFound); } - // recreate the HttpClient with redirection enabled since the first call suppressed redirection - using (client = GetHttpClient(false)) - using (HttpRequestMessage redirectRequest = GetRequest(response.Headers.Location, stripAuthorization:true)) + if (errorRecord != null) { - FillRequestStream(redirectRequest); - _cancelToken = new CancellationTokenSource(); - response = client.SendAsync(redirectRequest, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); + ThrowTerminatingError(errorRecord); } } - return response; + + // output ?? + if (PassThru && (OutFile == null)) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.OutFileMissing, + "WebCmdletOutFileMissingException"); + ThrowTerminatingError(error); + } } - internal virtual void UpdateSession(HttpResponseMessage response) + internal virtual void PrepareSession() { - if (response == null) { throw new ArgumentNullException("response"); } + // make sure we have a valid WebRequestSession object to work with + if (null == WebSession) + { + WebSession = new WebRequestSession(); + } + + if (null != SessionVariable) + { + // save the session back to the PS environment if requested + PSVariableIntrinsics vi = SessionState.PSVariable; + vi.Set(SessionVariable, WebSession); + } + + // + // handle credentials + // + if (null != Credential && Authentication == WebAuthenticationType.None) + { + // get the relevant NetworkCredential + NetworkCredential netCred = Credential.GetNetworkCredential(); + WebSession.Credentials = netCred; + + // supplying a credential overrides the UseDefaultCredentials setting + WebSession.UseDefaultCredentials = false; + } + else if ((null != Credential || null!= Token) && Authentication != WebAuthenticationType.None) + { + ProcessAuthentication(); + } + else if (UseDefaultCredentials) + { + WebSession.UseDefaultCredentials = true; + } + + + if (null != CertificateThumbprint) + { + X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates; + X509Certificate2Collection tbCollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByThumbprint, CertificateThumbprint, false); + if (tbCollection.Count == 0) + { + CryptographicException ex = new CryptographicException(WebCmdletStrings.ThumbprintNotFound); + throw ex; + } + foreach (X509Certificate2 tbCert in tbCollection) + { + X509Certificate certificate = (X509Certificate)tbCert; + WebSession.AddCertificate(certificate); + } + } + + if (null != Certificate) + { + WebSession.AddCertificate(Certificate); + } + + // + // handle the user agent + // + if (null != UserAgent) + { + // store the UserAgent string + WebSession.UserAgent = UserAgent; + } + + if (null != Proxy) + { + WebProxy webProxy = new WebProxy(Proxy); + webProxy.BypassProxyOnLocal = false; + if (null != ProxyCredential) + { + webProxy.Credentials = ProxyCredential.GetNetworkCredential(); + } + else if (ProxyUseDefaultCredentials) + { + // If both ProxyCredential and ProxyUseDefaultCredentials are passed, + // UseDefaultCredentials will overwrite the supplied credentials. + webProxy.UseDefaultCredentials = true; + } + WebSession.Proxy = webProxy; + } + + if (-1 < MaximumRedirection) + { + WebSession.MaximumRedirection = MaximumRedirection; + } + + // store the other supplied headers + if (null != Headers) + { + foreach (string key in Headers.Keys) + { + // add the header value (or overwrite it if already present) + WebSession.Headers[key] = Headers[key].ToString(); + } + } } #endregion Virtual Methods + #region Helper Properties + + internal string QualifiedOutFile + { + get { return (QualifyFilePath(OutFile)); } + } + + internal bool ShouldSaveToOutFile + { + get { return (!string.IsNullOrEmpty(OutFile)); } + } + + internal bool ShouldWriteToPipeline + { + get { return (!ShouldSaveToOutFile || PassThru); } + } + + #endregion Helper Properties + + #region Abstract Methods + + /// + /// Read the supplied WebResponse object and push the + /// resulting output into the pipeline. + /// + /// Instance of a WebResponse object to be processed + internal abstract void ProcessResponse(HttpResponseMessage response); + + #endregion Abstract Methods + #region Overrides /// @@ -662,6 +1233,158 @@ protected override void StopProcessing() #region Helper Methods + private HttpMethod GetHttpMethod(WebRequestMethod method) + { + switch (Method) + { + case WebRequestMethod.Default: + case WebRequestMethod.Get: + return HttpMethod.Get; + case WebRequestMethod.Head: + return HttpMethod.Head; + case WebRequestMethod.Post: + return HttpMethod.Post; + case WebRequestMethod.Put: + return HttpMethod.Put; + case WebRequestMethod.Delete: + return HttpMethod.Delete; + case WebRequestMethod.Trace: + return HttpMethod.Trace; + case WebRequestMethod.Options: + return HttpMethod.Options; + default: + // Merge and Patch + return new HttpMethod(Method.ToString().ToUpperInvariant()); + } + } + + private Uri PrepareUri(Uri uri) + { + uri = CheckProtocol(uri); + + // before creating the web request, + // preprocess Body if content is a dictionary and method is GET (set as query) + IDictionary bodyAsDictionary; + LanguagePrimitives.TryConvertTo(Body, out bodyAsDictionary); + if ((null != bodyAsDictionary) + && ((IsStandardMethodSet() && (Method == WebRequestMethod.Default || Method == WebRequestMethod.Get)) + || (IsCustomMethodSet() && CustomMethod.ToUpperInvariant() == "GET"))) + { + UriBuilder uriBuilder = new UriBuilder(uri); + if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) + { + uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + FormatDictionary(bodyAsDictionary); + } + else + { + uriBuilder.Query = FormatDictionary(bodyAsDictionary); + } + uri = uriBuilder.Uri; + // set body to null to prevent later FillRequestStream + Body = null; + } + + return uri; + } + + private Uri CheckProtocol(Uri uri) + { + if (null == uri) { throw new ArgumentNullException("uri"); } + + if (!uri.IsAbsoluteUri) + { + uri = new Uri("http://" + uri.OriginalString); + } + return (uri); + } + + private string QualifyFilePath(string path) + { + string resolvedFilePath = PathUtils.ResolveFilePath(path, this, false); + return resolvedFilePath; + } + + private string FormatDictionary(IDictionary content) + { + if (content == null) + throw new ArgumentNullException("content"); + + StringBuilder bodyBuilder = new StringBuilder(); + foreach (string key in content.Keys) + { + if (0 < bodyBuilder.Length) + { + bodyBuilder.Append("&"); + } + + object value = content[key]; + + // URLEncode the key and value + string encodedKey = WebUtility.UrlEncode(key); + string encodedValue = String.Empty; + if (null != value) + { + encodedValue = WebUtility.UrlEncode(value.ToString()); + } + + bodyBuilder.AppendFormat("{0}={1}", encodedKey, encodedValue); + } + return bodyBuilder.ToString(); + } + + private ErrorRecord GetValidationError(string msg, string errorId) + { + var ex = new ValidationMetadataException(msg); + var error = new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); + return (error); + } + + private ErrorRecord GetValidationError(string msg, string errorId, params object[] args) + { + msg = string.Format(CultureInfo.InvariantCulture, msg, args); + var ex = new ValidationMetadataException(msg); + var error = new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); + return (error); + } + + private bool IsStandardMethodSet() + { + return (ParameterSetName == "StandardMethod"); + } + + private bool IsCustomMethodSet() + { + return (ParameterSetName == "CustomMethod"); + } + + private string GetBasicAuthorizationHeader() + { + string unencoded = String.Format("{0}:{1}", Credential.UserName, Credential.GetNetworkCredential().Password); + Byte[] bytes = Encoding.UTF8.GetBytes(unencoded); + return String.Format("Basic {0}", Convert.ToBase64String(bytes)); + } + + private string GetBearerAuthorizationHeader() + { + return String.Format("Bearer {0}", new NetworkCredential(String.Empty, Token).Password); + } + + private void ProcessAuthentication() + { + if(Authentication == WebAuthenticationType.Basic) + { + WebSession.Headers["Authorization"] = GetBasicAuthorizationHeader(); + } + else if (Authentication == WebAuthenticationType.Bearer || Authentication == WebAuthenticationType.OAuth) + { + WebSession.Headers["Authorization"] = GetBearerAuthorizationHeader(); + } + else + { + Diagnostics.Assert(false, String.Format("Unrecognized Authentication value: {0}", Authentication)); + } + } + /// /// Sets the ContentLength property of the request and writes the specified content to the request's RequestStream. /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseHelper.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebResponseHelper.CoreClr.cs similarity index 97% rename from src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseHelper.CoreClr.cs rename to src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebResponseHelper.CoreClr.cs index 0949b343176..f625da9687f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseHelper.CoreClr.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebResponseHelper.CoreClr.cs @@ -9,7 +9,7 @@ namespace Microsoft.PowerShell.Commands { - internal static partial class WebResponseHelper + internal static class WebResponseHelper { internal static string GetCharacterSet(HttpResponseMessage response) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObject.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebResponseObject.Common.cs similarity index 57% rename from src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObject.CoreClr.cs rename to src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebResponseObject.Common.cs index 3145eef8c3a..3bc3878cae4 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObject.CoreClr.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebResponseObject.Common.cs @@ -1,22 +1,67 @@ -/********************************************************************++ +/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System; -using System.Text; -using System.Net.Http; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Net.Http; +using System.Text; namespace Microsoft.PowerShell.Commands { /// /// WebResponseObject /// - public partial class WebResponseObject + public class WebResponseObject { #region Properties + /// + /// gets or protected sets the Content property + /// + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public byte[] Content { get; protected set; } + + /// + /// gets the StatusCode property + /// + public int StatusCode + { + get { return (WebResponseHelper.GetStatusCode(BaseResponse)); } + } + + /// + /// gets the StatusDescription property + /// + public string StatusDescription + { + get { return (WebResponseHelper.GetStatusDescription(BaseResponse)); } + } + + private MemoryStream _rawContentStream; + /// + /// gets the RawContentStream property + /// + public MemoryStream RawContentStream + { + get { return (_rawContentStream); } + } + + /// + /// gets the RawContentLength property + /// + public long RawContentLength + { + get { return (null == RawContentStream ? -1 : RawContentStream.Length); } + } + + /// + /// gets or protected sets the RawContent property + /// + public string RawContent { get; protected set; } + /// /// gets or sets the BaseResponse property /// @@ -37,7 +82,6 @@ public Dictionary> Headers return _headers; } } - private Dictionary> _headers = null; /// @@ -45,7 +89,7 @@ public Dictionary> Headers /// public Dictionary RelationLink { get; internal set; } - #endregion + #endregion Properties #region Constructors @@ -73,6 +117,14 @@ public WebResponseObject(HttpResponseMessage response, Stream contentStream) #region Methods + /// + /// Reads the response content from the web response. + /// + private void InitializeContent() + { + this.Content = this.RawContentStream.ToArray(); + } + private void InitializeRawContent(HttpResponseMessage baseResponse) { StringBuilder raw = ContentHelper.GetRawContentHeader(baseResponse); @@ -86,6 +138,29 @@ private void InitializeRawContent(HttpResponseMessage baseResponse) this.RawContent = raw.ToString(); } + private bool IsPrintable(char c) + { + return (Char.IsLetterOrDigit(c) || Char.IsPunctuation(c) || Char.IsSeparator(c) || Char.IsSymbol(c) || Char.IsWhiteSpace(c)); + } + + /// + /// Returns the string representation of this web response. + /// + /// The string representation of this web response. + public sealed override string ToString() + { + char[] stringContent = System.Text.Encoding.ASCII.GetChars(Content); + for (int counter = 0; counter < stringContent.Length; counter++) + { + if (!IsPrintable(stringContent[counter])) + { + stringContent[counter] = '.'; + } + } + + return new string(stringContent); + } + private void SetResponse(HttpResponseMessage response, Stream contentStream) { if (null == response) { throw new ArgumentNullException("response"); } @@ -117,6 +192,6 @@ private void SetResponse(HttpResponseMessage response, Stream contentStream) _rawContentStream.Position = 0; } - #endregion + #endregion Methods } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObjectFactory.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebResponseObjectFactory.CoreClr.cs similarity index 100% rename from src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObjectFactory.CoreClr.cs rename to src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebResponseObjectFactory.CoreClr.cs