From 8dd0b043b86b4c1d4ab10b9b880148dda7266b91 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Thu, 9 Feb 2023 22:14:10 +0100 Subject: [PATCH 01/15] 2 ternary operators, 1 => --- .../Common/WebRequestPSCmdlet.Common.cs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) 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 index 7779295c84d..8ef91fc4d61 100644 --- 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 @@ -737,19 +737,10 @@ private static Uri CheckProtocol(Uri uri) { ArgumentNullException.ThrowIfNull(uri); - if (!uri.IsAbsoluteUri) - { - uri = new Uri("http://" + uri.OriginalString); - } - - return uri; + return uri.IsAbsoluteUri ? uri : new Uri("http://" + uri.OriginalString); } - private string QualifyFilePath(string path) - { - string resolvedFilePath = PathUtils.ResolveFilePath(filePath: path, command: this, isLiteralPath: true); - return resolvedFilePath; - } + private string QualifyFilePath(string path) => PathUtils.ResolveFilePath(filePath: path, command: this, isLiteralPath: true); private static string FormatDictionary(IDictionary content) { @@ -767,11 +758,7 @@ private static string FormatDictionary(IDictionary content) // URLEncode the key and value string encodedKey = WebUtility.UrlEncode(key); - string encodedValue = string.Empty; - if (value is not null) - { - encodedValue = WebUtility.UrlEncode(value.ToString()); - } + string encodedValue = value is null ? string.Empty : WebUtility.UrlEncode(value.ToString()); bodyBuilder.Append($"{encodedKey}={encodedValue}"); } From 7aab7615d21f26121704aad32dc70ef4cb0ef66c Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Thu, 9 Feb 2023 22:19:53 +0100 Subject: [PATCH 02/15] GetValidationError --- .../WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 index 8ef91fc4d61..887cb431d33 100644 --- 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 @@ -768,17 +768,15 @@ private static string FormatDictionary(IDictionary content) private ErrorRecord GetValidationError(string msg, string errorId) { - var ex = new ValidationMetadataException(msg); - var error = new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); - return error; + ValidationMetadataException ex = new(msg); + return new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); } 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; + ValidationMetadataException ex = new(msg); + return new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); } private string GetBasicAuthorizationHeader() From 105aab555d830a5bd579ce4ea35bd720e14a1b50 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Thu, 9 Feb 2023 22:41:16 +0100 Subject: [PATCH 03/15] replace var --- .../Common/WebRequestPSCmdlet.Common.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) 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 index 887cb431d33..a961bcf19e5 100644 --- 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 @@ -664,13 +664,13 @@ internal virtual void PrepareSession() { foreach (string key in Headers.Keys) { - var value = Headers[key]; + object value = Headers[key]; // null is not valid value for header. // We silently ignore header if value is null. if (value is not null) { - // add the header value (or overwrite it if already present) + // Add the header value (or overwrite it if already present) WebSession.Headers[key] = value.ToString(); } } @@ -962,7 +962,7 @@ internal virtual HttpRequestMessage GetRequest(Uri uri) HttpMethod httpMethod = string.IsNullOrEmpty(CustomMethod) ? GetHttpMethod(Method) : new HttpMethod(CustomMethod); // Create the base WebRequest object - var request = new HttpRequestMessage(httpMethod, requestUri); + HttpRequestMessage request = new(httpMethod, requestUri); if (HttpVersion is not null) { @@ -973,7 +973,7 @@ internal virtual HttpRequestMessage GetRequest(Uri uri) if (WebSession.Headers.Count > 0) { WebSession.ContentHeaders.Clear(); - foreach (var entry in WebSession.Headers) + foreach (KeyValuePair entry in WebSession.Headers) { if (HttpKnownHeaderNames.ContentHeaders.Contains(entry.Key)) { @@ -1026,7 +1026,7 @@ internal virtual HttpRequestMessage GetRequest(Uri uri) if (TransferEncoding is not null) { request.Headers.TransferEncodingChunked = true; - var headerValue = new TransferCodingHeaderValue(TransferEncoding); + TransferCodingHeaderValue headerValue = new(TransferEncoding); if (!request.Headers.TransferEncoding.Contains(headerValue)) { request.Headers.TransferEncoding.Add(headerValue); @@ -1037,7 +1037,7 @@ internal virtual HttpRequestMessage GetRequest(Uri uri) // If not, create a Range to request the entire file. if (Resume.IsPresent) { - var fileInfo = new FileInfo(QualifiedOutFile); + FileInfo fileInfo = new(QualifiedOutFile); if (fileInfo.Exists) { request.Headers.Range = new RangeHeaderValue(fileInfo.Length, null); @@ -1073,7 +1073,7 @@ internal virtual void FillRequestStream(HttpRequestMessage request) if (Form is not null) { - var formData = new MultipartFormDataContent(); + MultipartFormDataContent formData = new(); foreach (DictionaryEntry formEntry in Form) { // AddMultipartContent will handle PSObject unwrapping, Object type determination and enumerateing top level IEnumerables. @@ -1147,7 +1147,7 @@ internal virtual void FillRequestStream(HttpRequestMessage request) request.Content.Headers.Clear(); } - foreach (var entry in WebSession.ContentHeaders) + foreach (KeyValuePair entry in WebSession.ContentHeaders) { if (!string.IsNullOrWhiteSpace(entry.Value)) { @@ -1163,7 +1163,7 @@ internal virtual void FillRequestStream(HttpRequestMessage request) } catch (FormatException ex) { - var outerEx = new ValidationMetadataException(WebCmdletStrings.ContentTypeException, ex); + ValidationMetadataException outerEx = new(WebCmdletStrings.ContentTypeException, ex); ErrorRecord er = new(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, ContentType); ThrowTerminatingError(er); } @@ -1575,7 +1575,7 @@ internal void SetRequestContent(HttpRequestMessage request, string content) // would be used if Charset is not supplied in the Content-Type property. try { - var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(ContentType); + MediaTypeHeaderValue mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(ContentType); if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) { encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); @@ -1764,11 +1764,11 @@ private void AddMultipartContent(object fieldName, object fieldValue, MultipartF /// The Field Value to use for the private static StringContent GetMultipartStringContent(object fieldName, object fieldValue) { - var contentDisposition = new ContentDispositionHeaderValue("form-data"); + ContentDispositionHeaderValue contentDisposition = new("form-data"); // .NET does not enclose field names in quotes, however, modern browsers and curl do. contentDisposition.Name = "\"" + LanguagePrimitives.ConvertTo(fieldName) + "\""; - var result = new StringContent(LanguagePrimitives.ConvertTo(fieldValue)); + StringContent result = new(LanguagePrimitives.ConvertTo(fieldValue)); result.Headers.ContentDisposition = contentDisposition; return result; @@ -1781,11 +1781,12 @@ private static StringContent GetMultipartStringContent(object fieldName, object /// The to use for the private static StreamContent GetMultipartStreamContent(object fieldName, Stream stream) { - var contentDisposition = new ContentDispositionHeaderValue("form-data"); + ContentDispositionHeaderValue contentDisposition = new("form-data"); + // .NET does not enclose field names in quotes, however, modern browsers and curl do. contentDisposition.Name = "\"" + LanguagePrimitives.ConvertTo(fieldName) + "\""; - var result = new StreamContent(stream); + StreamContent result = new(stream); result.Headers.ContentDisposition = contentDisposition; result.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); @@ -1799,7 +1800,8 @@ private static StreamContent GetMultipartStreamContent(object fieldName, Stream /// The file to use for the private static StreamContent GetMultipartFileContent(object fieldName, FileInfo file) { - var result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open)); + StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open)); + // .NET does not enclose field names in quotes, however, modern browsers and curl do. result.Headers.ContentDisposition.FileName = "\"" + file.Name + "\""; From a061098f561f97c0c0d1286f585cad319b2d7a09 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Thu, 9 Feb 2023 22:45:19 +0100 Subject: [PATCH 04/15] remove var --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a961bcf19e5..20c0251513a 100644 --- 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 @@ -1749,7 +1749,7 @@ private void AddMultipartContent(object fieldName, object fieldValue, MultipartF // Treat the value as a collection and enumerate it if enumeration is true if (enumerate && fieldValue is IEnumerable items) { - foreach (var item in items) + foreach (object item in items) { // Recurse, but do not enumerate the next level. IEnumerables will be treated as single values. AddMultipartContent(fieldName: fieldName, fieldValue: item, formData: formData, enumerate: false); From c4cb0d14c590e02969bba95b655add569ae9b4dd Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Thu, 9 Feb 2023 23:50:27 +0100 Subject: [PATCH 05/15] throw --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 index 20c0251513a..45fc8caa481 100644 --- 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 @@ -613,8 +613,7 @@ internal virtual void PrepareSession() X509Certificate2Collection tbCollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByThumbprint, CertificateThumbprint, false); if (tbCollection.Count == 0) { - CryptographicException ex = new(WebCmdletStrings.ThumbprintNotFound); - throw ex; + throw new CryptographicException(WebCmdletStrings.ThumbprintNotFound); } foreach (X509Certificate2 tbCert in tbCollection) From 7d4bbf4487bd62c29291b8d094321bf76b49aabb Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Fri, 10 Feb 2023 02:43:12 +0100 Subject: [PATCH 06/15] var, null --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 45fc8caa481..b4e211bb286 100644 --- 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 @@ -780,7 +780,7 @@ private ErrorRecord GetValidationError(string msg, string errorId, params object private string GetBasicAuthorizationHeader() { - var password = new NetworkCredential(null, Credential.Password).Password; + string password = new NetworkCredential(null, Credential.Password).Password; string unencoded = string.Create(CultureInfo.InvariantCulture, $"{Credential.UserName}:{password}"); byte[] bytes = Encoding.UTF8.GetBytes(unencoded); return string.Create(CultureInfo.InvariantCulture, $"Basic {Convert.ToBase64String(bytes)}"); @@ -788,7 +788,7 @@ private string GetBasicAuthorizationHeader() private string GetBearerAuthorizationHeader() { - return string.Create(CultureInfo.InvariantCulture, $"Bearer {new NetworkCredential(string.Empty, Token).Password}"); + return string.Create(CultureInfo.InvariantCulture, $"Bearer {new NetworkCredential(null, Token).Password}"); } private void ProcessAuthentication() From f704ee314d6879daee88fdb5df6057238c54e319 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Fri, 10 Feb 2023 11:49:48 +0100 Subject: [PATCH 07/15] merge --- .../Common/WebRequestPSCmdlet.Common.cs | 837 +++++++++++++----- 1 file changed, 638 insertions(+), 199 deletions(-) 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 index b4e211bb286..031a17748e4 100644 --- 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 @@ -88,8 +88,47 @@ public enum WebSslProtocol /// /// Base class for Invoke-RestMethod and Invoke-WebRequest commands. /// - public abstract partial class WebRequestPSCmdlet : PSCmdlet + public abstract class WebRequestPSCmdlet : PSCmdlet { + #region Fields + + /// + /// Cancellation token source. + /// + internal CancellationTokenSource _cancelToken = null; + + /// + /// Automatically follow Rel Links. + /// + internal bool _followRelLink = false; + + /// + /// Maximum number of Rel Links to follow. + /// + internal int _maximumFollowRelLink = int.MaxValue; + + /// + /// Parse Rel Links. + /// + internal bool _parseRelLink = false; + + /// + /// Automatically follow Rel Links. + /// + internal Dictionary _relationLink = null; + + /// + /// The current size of the local file being resumed. + /// + private long _resumeFileSize = 0; + + /// + /// The remote endpoint returned a 206 status code indicating successful resume. + /// + private bool _resumeSuccess = false; + + #endregion Fields + #region Virtual Properties #region URI @@ -425,6 +464,213 @@ public virtual string CustomMethod #endregion Virtual Properties + #region Helper Properties + + internal string QualifiedOutFile => QualifyFilePath(OutFile); + + internal bool ShouldCheckHttpStatus => !SkipHttpErrorCheck; + + /// + /// Determines whether writing to a file should Resume and append rather than overwrite. + /// + internal bool ShouldResume => Resume.IsPresent && _resumeSuccess; + + internal bool ShouldSaveToOutFile => !string.IsNullOrEmpty(OutFile); + + internal bool ShouldWriteToPipeline => !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 + + /// + /// The main execution method for cmdlets derived from WebRequestPSCmdlet. + /// + protected override void ProcessRecord() + { + try + { + // Set cmdlet context for write progress + ValidateParameters(); + PrepareSession(); + + // If the request contains an authorization header and PreserveAuthorizationOnRedirect is not set, + // it needs to be stripped on the first redirect. + bool keepAuthorizationOnRedirect = PreserveAuthorizationOnRedirect.IsPresent + && WebSession.Headers.ContainsKey(HttpKnownHeaderNames.Authorization); + + bool handleRedirect = keepAuthorizationOnRedirect || AllowInsecureRedirect; + + using (HttpClient client = GetHttpClient(handleRedirect)) + { + int followedRelLink = 0; + Uri uri = Uri; + do + { + if (followedRelLink > 0) + { + string linkVerboseMsg = string.Format( + CultureInfo.CurrentCulture, + WebCmdletStrings.FollowingRelLinkVerboseMsg, + uri.AbsoluteUri); + + WriteVerbose(linkVerboseMsg); + } + + using (HttpRequestMessage request = GetRequest(uri)) + { + FillRequestStream(request); + try + { + long requestContentLength = request.Content is null ? 0 : request.Content.Headers.ContentLength.Value; + + string reqVerboseMsg = string.Format( + CultureInfo.CurrentCulture, + WebCmdletStrings.WebMethodInvocationVerboseMsg, + request.Version, + request.Method, + requestContentLength); + + WriteVerbose(reqVerboseMsg); + + using HttpResponseMessage response = GetResponse(client, request, handleRedirect); + + string contentType = ContentHelper.GetContentType(response); + string respVerboseMsg = string.Format( + CultureInfo.CurrentCulture, + WebCmdletStrings.WebResponseVerboseMsg, + response.Content.Headers.ContentLength, + contentType); + + WriteVerbose(respVerboseMsg); + + bool _isSuccess = response.IsSuccessStatusCode; + + // Check if the Resume range was not satisfiable because the file already completed downloading. + // This happens when the local file is the same size as the remote file. + if (Resume.IsPresent + && response.StatusCode == HttpStatusCode.RequestedRangeNotSatisfiable + && response.Content.Headers.ContentRange.HasLength + && response.Content.Headers.ContentRange.Length == _resumeFileSize) + { + _isSuccess = true; + WriteVerbose(string.Format( + CultureInfo.CurrentCulture, + WebCmdletStrings.OutFileWritingSkipped, + OutFile)); + + // Disable writing to the OutFile. + OutFile = null; + } + + if (ShouldCheckHttpStatus && !_isSuccess) + { + string message = string.Format( + CultureInfo.CurrentCulture, + WebCmdletStrings.ResponseStatusCodeFailure, + (int)response.StatusCode, + response.ReasonPhrase); + + HttpResponseException httpEx = new(message, response); + ErrorRecord er = new(httpEx, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); + string detailMsg = string.Empty; + StreamReader reader = null; + try + { + reader = new StreamReader(StreamHelper.GetResponseStream(response)); + detailMsg = FormatErrorMessage(reader.ReadToEnd(), contentType); + } + catch + { + // Catch all + } + finally + { + reader?.Dispose(); + } + + if (!string.IsNullOrEmpty(detailMsg)) + { + er.ErrorDetails = new ErrorDetails(detailMsg); + } + + ThrowTerminatingError(er); + } + + if (_parseRelLink || _followRelLink) + { + ParseLinkHeader(response, uri); + } + + ProcessResponse(response); + UpdateSession(response); + + // If we hit our maximum redirection count, generate an error. + // Errors with redirection counts of greater than 0 are handled automatically by .NET, but are + // impossible to detect programmatically when we hit this limit. By handling this ourselves + // (and still writing out the result), users can debug actual HTTP redirect problems. + if (WebSession.MaximumRedirection == 0 && IsRedirectCode(response.StatusCode)) + { + ErrorRecord er = new(new InvalidOperationException(), "MaximumRedirectExceeded", ErrorCategory.InvalidOperation, request); + er.ErrorDetails = new ErrorDetails(WebCmdletStrings.MaximumRedirectionCountExceeded); + WriteError(er); + } + } + catch (HttpRequestException ex) + { + ErrorRecord er = new(ex, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); + if (ex.InnerException is not null) + { + er.ErrorDetails = new ErrorDetails(ex.InnerException.Message); + } + + ThrowTerminatingError(er); + } + + if (_followRelLink) + { + if (!_relationLink.ContainsKey("next")) + { + return; + } + + uri = new Uri(_relationLink["next"]); + followedRelLink++; + } + } + } + while (_followRelLink && (followedRelLink < _maximumFollowRelLink)); + } + } + catch (CryptographicException ex) + { + ErrorRecord er = new(ex, "WebCmdletCertificateException", ErrorCategory.SecurityError, null); + ThrowTerminatingError(er); + } + catch (NotSupportedException ex) + { + ErrorRecord er = new(ex, "WebCmdletIEDomNotSupportedException", ErrorCategory.NotImplemented, null); + ThrowTerminatingError(er); + } + } + + /// + /// To implement ^C. + /// + protected override void StopProcessing() => _cancelToken?.Cancel(); + + #endregion Overrides + #region Virtual Methods internal virtual void ValidateParameters() @@ -684,6 +930,7 @@ internal virtual void PrepareSession() } } + #endregion Virtual Methods #region Helper Properties @@ -898,16 +1145,231 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet #region Virtual Methods - internal virtual HttpClient GetHttpClient(bool handleRedirect) - { - HttpClientHandler handler = new(); - handler.CookieContainer = WebSession.Cookies; - handler.AutomaticDecompression = DecompressionMethods.All; + + #endregion Virtual Methods - // Set the credentials used by this request - if (WebSession.UseDefaultCredentials) - { - // The UseDefaultCredentials flag overrides other supplied credentials + #region Helper Properties + + internal string QualifiedOutFile => QualifyFilePath(OutFile); + + internal bool ShouldSaveToOutFile => !string.IsNullOrEmpty(OutFile); + + internal bool ShouldWriteToPipeline => !ShouldSaveToOutFile || PassThru; + + internal bool ShouldCheckHttpStatus => !SkipHttpErrorCheck; + + /// + /// Determines whether writing to a file should Resume and append rather than overwrite. + /// + internal bool ShouldResume => Resume.IsPresent && _resumeSuccess; + + #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) + LanguagePrimitives.TryConvertTo(Body, out IDictionary bodyAsDictionary); + if (bodyAsDictionary is not null && (Method == WebRequestMethod.Default || Method == WebRequestMethod.Get || CustomMethod == "GET")) + { + UriBuilder uriBuilder = new(uri); + if (uriBuilder.Query is not null && uriBuilder.Query.Length > 1) + { + uriBuilder.Query = string.Concat(uriBuilder.Query.AsSpan(1), "&", FormatDictionary(bodyAsDictionary)); + } + else + { + uriBuilder.Query = FormatDictionary(bodyAsDictionary); + } + + uri = uriBuilder.Uri; + + // Set body to null to prevent later FillRequestStream + Body = null; + } + + return uri; + } + + private static Uri CheckProtocol(Uri uri) + { + ArgumentNullException.ThrowIfNull(uri); + + return uri.IsAbsoluteUri ? uri : new Uri("http://" + uri.OriginalString); + } + + private string QualifyFilePath(string path) => PathUtils.ResolveFilePath(filePath: path, command: this, isLiteralPath: true); + + private static string FormatDictionary(IDictionary content) + { + ArgumentNullException.ThrowIfNull(content); + + StringBuilder bodyBuilder = new(); + foreach (string key in content.Keys) + { + if (bodyBuilder.Length > 0) + { + bodyBuilder.Append('&'); + } + + object value = content[key]; + + // URLEncode the key and value + string encodedKey = WebUtility.UrlEncode(key); + string encodedValue = value is null ? string.Empty : WebUtility.UrlEncode(value.ToString()); + + bodyBuilder.Append($"{encodedKey}={encodedValue}"); + } + + return bodyBuilder.ToString(); + } + + private ErrorRecord GetValidationError(string msg, string errorId) + { + ValidationMetadataException ex = new(msg); + return new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); + } + + private ErrorRecord GetValidationError(string msg, string errorId, params object[] args) + { + msg = string.Format(CultureInfo.InvariantCulture, msg, args); + ValidationMetadataException ex = new(msg); + return new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); + } + + private string GetBasicAuthorizationHeader() + { + string password = new NetworkCredential(null, Credential.Password).Password; + string unencoded = string.Create(CultureInfo.InvariantCulture, $"{Credential.UserName}:{password}"); + byte[] bytes = Encoding.UTF8.GetBytes(unencoded); + return string.Create(CultureInfo.InvariantCulture, $"Basic {Convert.ToBase64String(bytes)}"); + } + + private string GetBearerAuthorizationHeader() + { + return string.Create(CultureInfo.InvariantCulture, $"Bearer {new NetworkCredential(null, 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.Create(CultureInfo.InvariantCulture, $"Unrecognized Authentication value: {Authentication}")); + } + } + + #endregion Helper Methods + } + + // TODO: Merge Partials + + /// + /// Exception class for webcmdlets to enable returning HTTP error response. + /// + public sealed class HttpResponseException : HttpRequestException + { + /// + /// Initializes a new instance of the class. + /// + /// Message for the exception. + /// Response from the HTTP server. + public HttpResponseException(string message, HttpResponseMessage response) : base(message, inner: null, response.StatusCode) + { + Response = response; + } + + /// + /// HTTP error response. + /// + public HttpResponseMessage Response { get; } + } + + /// + /// Base class for Invoke-RestMethod and Invoke-WebRequest commands. + /// + public abstract partial class WebRequestPSCmdlet : PSCmdlet + { + #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 + + /// + /// Cancellation token source. + /// + internal CancellationTokenSource _cancelToken = null; + + /// + /// Parse Rel Links. + /// + internal bool _parseRelLink = false; + + /// + /// Automatically follow Rel Links. + /// + internal bool _followRelLink = false; + + /// + /// Automatically follow Rel Links. + /// + internal Dictionary _relationLink = null; + + /// + /// Maximum number of Rel Links to follow. + /// + internal int _maximumFollowRelLink = int.MaxValue; + + /// + /// The remote endpoint returned a 206 status code indicating successful resume. + /// + private bool _resumeSuccess = false; + + /// + /// The current size of the local file being resumed. + /// + private long _resumeFileSize = 0; + + private static HttpMethod GetHttpMethod(WebRequestMethod method) => method switch + { + WebRequestMethod.Default or WebRequestMethod.Get => HttpMethod.Get, + WebRequestMethod.Delete => HttpMethod.Delete, + WebRequestMethod.Head => HttpMethod.Head, + WebRequestMethod.Patch => HttpMethod.Patch, + WebRequestMethod.Post => HttpMethod.Post, + WebRequestMethod.Put => HttpMethod.Put, + WebRequestMethod.Options => HttpMethod.Options, + WebRequestMethod.Trace => HttpMethod.Trace, + _ => new HttpMethod(method.ToString().ToUpperInvariant()) + }; + + #region Virtual Methods + + internal virtual HttpClient GetHttpClient(bool handleRedirect) + { + HttpClientHandler handler = new(); + handler.CookieContainer = WebSession.Cookies; + handler.AutomaticDecompression = DecompressionMethods.All; + + // Set the credentials used by this request + if (WebSession.UseDefaultCredentials) + { + // The UseDefaultCredentials flag overrides other supplied credentials handler.UseDefaultCredentials = true; } else if (WebSession.Credentials is not null) @@ -972,7 +1434,7 @@ internal virtual HttpRequestMessage GetRequest(Uri uri) if (WebSession.Headers.Count > 0) { WebSession.ContentHeaders.Clear(); - foreach (KeyValuePair entry in WebSession.Headers) + foreach (var entry in WebSession.Headers) { if (HttpKnownHeaderNames.ContentHeaders.Contains(entry.Key)) { @@ -1146,7 +1608,7 @@ internal virtual void FillRequestStream(HttpRequestMessage request) request.Content.Headers.Clear(); } - foreach (KeyValuePair entry in WebSession.ContentHeaders) + foreach (KeyValuePair entry in WebSession.ContentHeaders) { if (!string.IsNullOrWhiteSpace(entry.Value)) { @@ -1171,45 +1633,6 @@ internal virtual void FillRequestStream(HttpRequestMessage request) } } - // Returns true if the status code is one of the supported redirection codes. - private static bool IsRedirectCode(HttpStatusCode code) - { - int intCode = (int)code; - return - ( - (intCode >= 300 && intCode < 304) || - intCode == 307 || - intCode == 308 - ); - } - - // Returns true if the status code is a redirection code and the action requires switching from POST to GET on redirection. - // NOTE: Some of these status codes map to the same underlying value but spelling them out for completeness. - private static bool IsRedirectToGet(HttpStatusCode code) - { - return - ( - code == HttpStatusCode.Found || - code == HttpStatusCode.Moved || - code == HttpStatusCode.Redirect || - code == HttpStatusCode.RedirectMethod || - code == HttpStatusCode.SeeOther || - code == HttpStatusCode.Ambiguous || - code == HttpStatusCode.MultipleChoices - ); - } - - // Returns true if the status code shows a server or client error and MaximumRetryCount > 0 - private bool ShouldRetry(HttpStatusCode code) - { - int intCode = (int)code; - - return - ( - (intCode == 304 || (intCode >= 400 && intCode <= 599)) && WebSession.MaximumRetryCount > 0 - ); - } - internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestMessage request, bool handleRedirect) { ArgumentNullException.ThrowIfNull(client); @@ -1352,179 +1775,121 @@ internal virtual void UpdateSession(HttpResponseMessage response) #endregion Virtual Methods - #region Overrides - - /// - /// The main execution method for cmdlets derived from WebRequestPSCmdlet. - /// - protected override void ProcessRecord() + #region Helper Methods + private Uri PrepareUri(Uri uri) { - try - { - // Set cmdlet context for write progress - ValidateParameters(); - PrepareSession(); - - // If the request contains an authorization header and PreserveAuthorizationOnRedirect is not set, - // it needs to be stripped on the first redirect. - bool keepAuthorizationOnRedirect = PreserveAuthorizationOnRedirect.IsPresent - && WebSession.Headers.ContainsKey(HttpKnownHeaderNames.Authorization); - - bool handleRedirect = keepAuthorizationOnRedirect || AllowInsecureRedirect; - - using (HttpClient client = GetHttpClient(handleRedirect)) - { - int followedRelLink = 0; - Uri uri = Uri; - do - { - if (followedRelLink > 0) - { - string linkVerboseMsg = string.Format( - CultureInfo.CurrentCulture, - WebCmdletStrings.FollowingRelLinkVerboseMsg, - uri.AbsoluteUri); - - WriteVerbose(linkVerboseMsg); - } - - using (HttpRequestMessage request = GetRequest(uri)) - { - FillRequestStream(request); - try - { - long requestContentLength = request.Content is null ? 0 : request.Content.Headers.ContentLength.Value; + uri = CheckProtocol(uri); - string reqVerboseMsg = string.Format( - CultureInfo.CurrentCulture, - WebCmdletStrings.WebMethodInvocationVerboseMsg, - request.Version, - request.Method, - requestContentLength); + // Before creating the web request, + // preprocess Body if content is a dictionary and method is GET (set as query) + LanguagePrimitives.TryConvertTo(Body, out IDictionary bodyAsDictionary); + if (bodyAsDictionary is not null && (Method == WebRequestMethod.Default || Method == WebRequestMethod.Get || CustomMethod == "GET")) + { + UriBuilder uriBuilder = new(uri); + if (uriBuilder.Query is not null && uriBuilder.Query.Length > 1) + { + uriBuilder.Query = string.Concat(uriBuilder.Query.AsSpan(1), "&", FormatDictionary(bodyAsDictionary)); + } + else + { + uriBuilder.Query = FormatDictionary(bodyAsDictionary); + } - WriteVerbose(reqVerboseMsg); + uri = uriBuilder.Uri; - using HttpResponseMessage response = GetResponse(client, request, handleRedirect); + // Set body to null to prevent later FillRequestStream + Body = null; + } - string contentType = ContentHelper.GetContentType(response); - string respVerboseMsg = string.Format( - CultureInfo.CurrentCulture, - WebCmdletStrings.WebResponseVerboseMsg, - response.Content.Headers.ContentLength, - contentType); + return uri; + } - WriteVerbose(respVerboseMsg); + private static Uri CheckProtocol(Uri uri) + { + ArgumentNullException.ThrowIfNull(uri); - bool _isSuccess = response.IsSuccessStatusCode; + return uri.IsAbsoluteUri ? uri : new Uri("http://" + uri.OriginalString); + } - // Check if the Resume range was not satisfiable because the file already completed downloading. - // This happens when the local file is the same size as the remote file. - if (Resume.IsPresent - && response.StatusCode == HttpStatusCode.RequestedRangeNotSatisfiable - && response.Content.Headers.ContentRange.HasLength - && response.Content.Headers.ContentRange.Length == _resumeFileSize) - { - _isSuccess = true; - WriteVerbose(string.Format( - CultureInfo.CurrentCulture, - WebCmdletStrings.OutFileWritingSkipped, - OutFile)); + private string QualifyFilePath(string path) => PathUtils.ResolveFilePath(filePath: path, command: this, isLiteralPath: true); - // Disable writing to the OutFile. - OutFile = null; - } + private static string FormatDictionary(IDictionary content) + { + ArgumentNullException.ThrowIfNull(content); - if (ShouldCheckHttpStatus && !_isSuccess) - { - string message = string.Format( - CultureInfo.CurrentCulture, - WebCmdletStrings.ResponseStatusCodeFailure, - (int)response.StatusCode, - response.ReasonPhrase); + StringBuilder bodyBuilder = new(); + foreach (string key in content.Keys) + { + if (bodyBuilder.Length > 0) + { + bodyBuilder.Append('&'); + } - HttpResponseException httpEx = new(message, response); - ErrorRecord er = new(httpEx, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); - string detailMsg = string.Empty; - StreamReader reader = null; - try - { - reader = new StreamReader(StreamHelper.GetResponseStream(response)); - detailMsg = FormatErrorMessage(reader.ReadToEnd(), contentType); - } - catch - { - // Catch all - } - finally - { - reader?.Dispose(); - } + object value = content[key]; - if (!string.IsNullOrEmpty(detailMsg)) - { - er.ErrorDetails = new ErrorDetails(detailMsg); - } + // URLEncode the key and value + string encodedKey = WebUtility.UrlEncode(key); + string encodedValue = value is null ? string.Empty : WebUtility.UrlEncode(value.ToString()); - ThrowTerminatingError(er); - } + bodyBuilder.Append($"{encodedKey}={encodedValue}"); + } - if (_parseRelLink || _followRelLink) - { - ParseLinkHeader(response, uri); - } + return bodyBuilder.ToString(); + } - ProcessResponse(response); - UpdateSession(response); + private ErrorRecord GetValidationError(string msg, string errorId) + { + ValidationMetadataException ex = new(msg); + return new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); + } - // If we hit our maximum redirection count, generate an error. - // Errors with redirection counts of greater than 0 are handled automatically by .NET, but are - // impossible to detect programmatically when we hit this limit. By handling this ourselves - // (and still writing out the result), users can debug actual HTTP redirect problems. - if (WebSession.MaximumRedirection == 0 && IsRedirectCode(response.StatusCode)) - { - ErrorRecord er = new(new InvalidOperationException(), "MaximumRedirectExceeded", ErrorCategory.InvalidOperation, request); - er.ErrorDetails = new ErrorDetails(WebCmdletStrings.MaximumRedirectionCountExceeded); - WriteError(er); - } - } - catch (HttpRequestException ex) - { - ErrorRecord er = new(ex, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); - if (ex.InnerException is not null) - { - er.ErrorDetails = new ErrorDetails(ex.InnerException.Message); - } + private ErrorRecord GetValidationError(string msg, string errorId, params object[] args) + { + msg = string.Format(CultureInfo.InvariantCulture, msg, args); + ValidationMetadataException ex = new(msg); + return new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); + } - ThrowTerminatingError(er); - } + private string GetBasicAuthorizationHeader() + { + string password = new NetworkCredential(string.Empty, Credential.Password).Password; + string unencoded = string.Create(CultureInfo.InvariantCulture, $"{Credential.UserName}:{password}"); + byte[] bytes = Encoding.UTF8.GetBytes(unencoded); + return string.Create(CultureInfo.InvariantCulture, $"Basic {Convert.ToBase64String(bytes)}"); + } - if (_followRelLink) - { - if (!_relationLink.ContainsKey("next")) - { - return; - } + private string GetBearerAuthorizationHeader() + { + return string.Create(CultureInfo.InvariantCulture, $"Bearer {new NetworkCredential(string.Empty, Token).Password}"); + } - uri = new Uri(_relationLink["next"]); - followedRelLink++; - } - } - } - while (_followRelLink && (followedRelLink < _maximumFollowRelLink)); - } + private void ProcessAuthentication() + { + if (Authentication == WebAuthenticationType.Basic) + { + WebSession.Headers["Authorization"] = GetBasicAuthorizationHeader(); } - catch (CryptographicException ex) + else if (Authentication == WebAuthenticationType.Bearer || Authentication == WebAuthenticationType.OAuth) { - ErrorRecord er = new(ex, "WebCmdletCertificateException", ErrorCategory.SecurityError, null); - ThrowTerminatingError(er); + WebSession.Headers["Authorization"] = GetBearerAuthorizationHeader(); } - catch (NotSupportedException ex) + else { - ErrorRecord er = new(ex, "WebCmdletIEDomNotSupportedException", ErrorCategory.NotImplemented, null); - ThrowTerminatingError(er); + Diagnostics.Assert(false, string.Create(CultureInfo.InvariantCulture, $"Unrecognized Authentication value: {Authentication}")); } } + + /// + /// To implement ^C. + /// + protected override void StopProcessing() => _cancelToken?.Cancel(); + + #endregion Overrides + + #region Helper Methods + + /// /// To implement ^C. /// @@ -1708,7 +2073,7 @@ internal void ParseLinkHeader(HttpResponseMessage response, System.Uri requestUr /// /// The Field Name to use. /// The Field Value to use. - /// The > to update. + /// The to update. /// If true, collection types in will be enumerated. If false, collections will be treated as single value. private void AddMultipartContent(object fieldName, object fieldValue, MultipartFormDataContent formData, bool enumerate) { @@ -1764,6 +2129,7 @@ private void AddMultipartContent(object fieldName, object fieldValue, MultipartF private static StringContent GetMultipartStringContent(object fieldName, object fieldValue) { ContentDispositionHeaderValue contentDisposition = new("form-data"); + // .NET does not enclose field names in quotes, however, modern browsers and curl do. contentDisposition.Name = "\"" + LanguagePrimitives.ConvertTo(fieldName) + "\""; @@ -1860,6 +2226,79 @@ private static string FormatErrorMessage(string error, string contentType) return formattedError; } + // Returns true if the status code is one of the supported redirection codes. + private static bool IsRedirectCode(HttpStatusCode code) + { + int intCode = (int)code; + return + ( + (intCode >= 300 && intCode < 304) || + intCode == 307 || + intCode == 308 + ); + } + + // Returns true if the status code is a redirection code and the action requires switching from POST to GET on redirection. + // NOTE: Some of these status codes map to the same underlying value but spelling them out for completeness. + private static bool IsRedirectToGet(HttpStatusCode code) + { + return + ( + code == HttpStatusCode.Found || + code == HttpStatusCode.Moved || + code == HttpStatusCode.Redirect || + code == HttpStatusCode.RedirectMethod || + code == HttpStatusCode.SeeOther || + code == HttpStatusCode.Ambiguous || + code == HttpStatusCode.MultipleChoices + ); + } + + // Returns true if the status code shows a server or client error and MaximumRetryCount > 0 + private bool ShouldRetry(HttpStatusCode code) + { + int intCode = (int)code; + + return + ( + (intCode == 304 || (intCode >= 400 && intCode <= 599)) && WebSession.MaximumRetryCount > 0 + ); + } + + private static HttpMethod GetHttpMethod(WebRequestMethod method) => method switch + { + WebRequestMethod.Default or WebRequestMethod.Get => HttpMethod.Get, + WebRequestMethod.Delete => HttpMethod.Delete, + WebRequestMethod.Head => HttpMethod.Head, + WebRequestMethod.Patch => HttpMethod.Patch, + WebRequestMethod.Post => HttpMethod.Post, + WebRequestMethod.Put => HttpMethod.Put, + WebRequestMethod.Options => HttpMethod.Options, + WebRequestMethod.Trace => HttpMethod.Trace, + _ => new HttpMethod(method.ToString().ToUpperInvariant()) + }; + #endregion Helper Methods } + + /// + /// Exception class for webcmdlets to enable returning HTTP error response. + /// + public sealed class HttpResponseException : HttpRequestException + { + /// + /// Initializes a new instance of the class. + /// + /// Message for the exception. + /// Response from the HTTP server. + public HttpResponseException(string message, HttpResponseMessage response) : base(message, inner: null, response.StatusCode) + { + Response = response; + } + + /// + /// HTTP error response. + /// + public HttpResponseMessage Response { get; } + } } From 7d06deb8b9b9bb1ba659282d9dba2b0973493986 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:06:47 +0100 Subject: [PATCH 08/15] is-as --> is --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 index 247506a04c0..4318c3580f9 100644 --- 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 @@ -1518,9 +1518,8 @@ internal void SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) byte[] bytes = null; XmlDocument doc = xmlNode as XmlDocument; - if (doc?.FirstChild is XmlDeclaration) + if (doc?.FirstChild is XmlDeclaration decl) { - XmlDeclaration decl = doc.FirstChild as XmlDeclaration; Encoding encoding = Encoding.GetEncoding(decl.Encoding); bytes = StreamHelper.EncodeToBytes(doc.OuterXml, encoding); } @@ -1740,9 +1739,8 @@ private static string FormatErrorMessage(string error, string contentType) OmitXmlDeclaration = true }; - if (doc.FirstChild is XmlDeclaration) + if (doc.FirstChild is XmlDeclaration decl) { - XmlDeclaration decl = doc.FirstChild as XmlDeclaration; settings.Encoding = Encoding.GetEncoding(decl.Encoding); } From 08733c4f4f55747d02b56021b1b87b417e851bfa Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:36:06 +0100 Subject: [PATCH 09/15] remove extra space --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 4318c3580f9..0b32d3d4e7e 100644 --- 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 @@ -1178,7 +1178,7 @@ internal virtual void FillRequestStream(HttpRequestMessage request) request.Content.Headers.Clear(); } - foreach (KeyValuePair entry in WebSession.ContentHeaders) + foreach (KeyValuePair entry in WebSession.ContentHeaders) { if (!string.IsNullOrWhiteSpace(entry.Value)) { From ddaf48027d342ba2a7bc3a9605f4316b581bb230 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:46:05 +0100 Subject: [PATCH 10/15] remove redundant ArgumentNullException.ThrowIfNull(request); --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 6 ------ 1 file changed, 6 deletions(-) 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 index 0b32d3d4e7e..49d8146a9b8 100644 --- 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 @@ -1460,7 +1460,6 @@ private void ProcessAuthentication() /// internal void SetRequestContent(HttpRequestMessage request, byte[] content) { - ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(content); ByteArrayContent byteArrayContent = new(content); @@ -1478,7 +1477,6 @@ internal void SetRequestContent(HttpRequestMessage request, byte[] content) /// internal void SetRequestContent(HttpRequestMessage request, string content) { - ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(content); Encoding encoding = null; @@ -1513,7 +1511,6 @@ internal void SetRequestContent(HttpRequestMessage request, string content) internal void SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) { - ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(xmlNode); byte[] bytes = null; @@ -1544,7 +1541,6 @@ internal void SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) /// internal void SetRequestContent(HttpRequestMessage request, Stream contentStream) { - ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(contentStream); StreamContent streamContent = new(contentStream); @@ -1562,7 +1558,6 @@ internal void SetRequestContent(HttpRequestMessage request, Stream contentStream /// internal void SetRequestContent(HttpRequestMessage request, MultipartFormDataContent multipartContent) { - ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(multipartContent); // Content headers will be set by MultipartFormDataContent which will throw unless we clear them first @@ -1573,7 +1568,6 @@ internal void SetRequestContent(HttpRequestMessage request, MultipartFormDataCon internal void SetRequestContent(HttpRequestMessage request, IDictionary content) { - ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(content); string body = FormatDictionary(content); From c61a83db1a9698421e5e024938400bc9ab0f7d58 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sat, 11 Feb 2023 10:27:13 +0100 Subject: [PATCH 11/15] revert --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 4 ++++ 1 file changed, 4 insertions(+) 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 index 49d8146a9b8..39ae6ffa04c 100644 --- 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 @@ -1460,6 +1460,7 @@ private void ProcessAuthentication() /// internal void SetRequestContent(HttpRequestMessage request, byte[] content) { + ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(content); ByteArrayContent byteArrayContent = new(content); @@ -1477,6 +1478,7 @@ internal void SetRequestContent(HttpRequestMessage request, byte[] content) /// internal void SetRequestContent(HttpRequestMessage request, string content) { + ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(content); Encoding encoding = null; @@ -1541,6 +1543,7 @@ internal void SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) /// internal void SetRequestContent(HttpRequestMessage request, Stream contentStream) { + ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(contentStream); StreamContent streamContent = new(contentStream); @@ -1568,6 +1571,7 @@ internal void SetRequestContent(HttpRequestMessage request, MultipartFormDataCon internal void SetRequestContent(HttpRequestMessage request, IDictionary content) { + ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(content); string body = FormatDictionary(content); From 73fc93abe9054bca8d3ac7a438cf367265ac762a Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sat, 11 Feb 2023 10:40:25 +0100 Subject: [PATCH 12/15] forgot 2 --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 2 ++ 1 file changed, 2 insertions(+) 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 index 39ae6ffa04c..0b32d3d4e7e 100644 --- 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 @@ -1513,6 +1513,7 @@ internal void SetRequestContent(HttpRequestMessage request, string content) internal void SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) { + ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(xmlNode); byte[] bytes = null; @@ -1561,6 +1562,7 @@ internal void SetRequestContent(HttpRequestMessage request, Stream contentStream /// internal void SetRequestContent(HttpRequestMessage request, MultipartFormDataContent multipartContent) { + ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(multipartContent); // Content headers will be set by MultipartFormDataContent which will throw unless we clear them first From ae07c44c46bd2ebcb80e6dd2a2a192cd1a2fabe2 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sat, 11 Feb 2023 13:41:54 +0100 Subject: [PATCH 13/15] ternary operator --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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 index 0b32d3d4e7e..2e65a4b1a3c 100644 --- 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 @@ -1116,13 +1116,8 @@ internal virtual void FillRequestStream(HttpRequestMessage request) else if (Body is not null) { // Coerce body into a usable form - object content = Body; - // Make sure we're using the base object of the body, not the PSObject wrapper - if (Body is PSObject psBody) - { - content = psBody.BaseObject; - } + object content = Body is PSObject psBody ? psBody.BaseObject : Body; switch (content) { From e08af2b11a74a0fd2792ceec7c5ab56ef740b5a7 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sat, 11 Feb 2023 14:21:38 +0100 Subject: [PATCH 14/15] request.Content --- .../WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) 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 index 2e65a4b1a3c..b8f493a3890 100644 --- 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 @@ -1458,8 +1458,7 @@ internal void SetRequestContent(HttpRequestMessage request, byte[] content) ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(content); - ByteArrayContent byteArrayContent = new(content); - request.Content = byteArrayContent; + request.Content = new ByteArrayContent(content); } /// @@ -1502,8 +1501,7 @@ internal void SetRequestContent(HttpRequestMessage request, string content) } byte[] bytes = StreamHelper.EncodeToBytes(content, encoding); - ByteArrayContent byteArrayContent = new(bytes); - request.Content = byteArrayContent; + request.Content = new ByteArrayContent(bytes); } internal void SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) @@ -1523,9 +1521,7 @@ internal void SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) bytes = StreamHelper.EncodeToBytes(xmlNode.OuterXml, encoding: null); } - ByteArrayContent byteArrayContent = new(bytes); - - request.Content = byteArrayContent; + request.Content = new ByteArrayContent(bytes); } /// @@ -1542,8 +1538,7 @@ internal void SetRequestContent(HttpRequestMessage request, Stream contentStream ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(contentStream); - StreamContent streamContent = new(contentStream); - request.Content = streamContent; + request.Content = new StreamContent(contentStream); } /// From a4bb533276008d23f4006464f5e7b662a483cee8 Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Sat, 11 Feb 2023 14:27:17 +0100 Subject: [PATCH 15/15] Fix comments --- .../Common/WebRequestPSCmdlet.Common.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 index b8f493a3890..4069a061c2b 100644 --- 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 @@ -1445,12 +1445,12 @@ private void ProcessAuthentication() } /// - /// Sets the ContentLength property of the request and writes the specified content to the request's RequestStream. + /// Writes the specified content to the request's RequestStream. /// /// The WebRequest who's content is to be set. /// A byte array containing the content data. /// - /// Because this function sets the request's ContentLength property and writes content data into the request's stream, + /// Because this function writes content data into the request's stream, /// it should be called one time maximum on a given request. /// internal void SetRequestContent(HttpRequestMessage request, byte[] content) @@ -1462,12 +1462,12 @@ internal void SetRequestContent(HttpRequestMessage request, byte[] content) } /// - /// Sets the ContentLength property of the request and writes the specified content to the request's RequestStream. + /// Writes the specified content to the request's RequestStream. /// /// The WebRequest who's content is to be set. /// A String object containing the content data. /// - /// Because this function sets the request's ContentLength property and writes content data into the request's stream, + /// Because this function writes content data into the request's stream, /// it should be called one time maximum on a given request. /// internal void SetRequestContent(HttpRequestMessage request, string content) @@ -1525,12 +1525,12 @@ internal void SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) } /// - /// Sets the ContentLength property of the request and writes the specified content to the request's RequestStream. + /// Writes the specified content to the request's RequestStream. /// /// The WebRequest who's content is to be set. /// A Stream object containing the content data. /// - /// Because this function sets the request's ContentLength property and writes content data into the request's stream, + /// Because this function writes content data into the request's stream, /// it should be called one time maximum on a given request. /// internal void SetRequestContent(HttpRequestMessage request, Stream contentStream) @@ -1542,12 +1542,12 @@ internal void SetRequestContent(HttpRequestMessage request, Stream contentStream } /// - /// Sets the ContentLength property of the request and writes the specified content to the request's RequestStream. + /// Writes the specified content to the request's RequestStream. /// /// The WebRequest who's content is to be set. /// A MultipartFormDataContent object containing multipart/form-data content. /// - /// Because this function sets the request's ContentLength property and writes content data into the request's stream, + /// Because this function writes content data into the request's stream, /// it should be called one time maximum on a given request. /// internal void SetRequestContent(HttpRequestMessage request, MultipartFormDataContent multipartContent)