Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public enum WebSslProtocol
/// <summary>
/// Base class for Invoke-RestMethod and Invoke-WebRequest commands.
/// </summary>
public abstract class WebRequestPSCmdlet : PSCmdlet
public abstract class WebRequestPSCmdlet : PSCmdlet, IDisposable
{
#region Fields

Expand Down Expand Up @@ -132,6 +132,11 @@ public abstract class WebRequestPSCmdlet : PSCmdlet
/// </summary>
private bool _resumeSuccess = false;

/// <summary>
/// True if the Dispose() method has already been called to cleanup Disposable fields.
/// </summary>
private bool _disposed = false;

#endregion Fields

#region Virtual Properties
Expand Down Expand Up @@ -522,7 +527,7 @@ protected override void ProcessRecord()

bool handleRedirect = keepAuthorizationOnRedirect || AllowInsecureRedirect || PreserveHttpMethodOnRedirect;

using HttpClient client = GetHttpClient(handleRedirect);
HttpClient client = GetHttpClient(handleRedirect);

int followedRelLink = 0;
Uri uri = Uri;
Expand Down Expand Up @@ -639,7 +644,7 @@ protected override void ProcessRecord()
// 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))
if (_maximumRedirection == 0 && IsRedirectCode(response.StatusCode))
{
ErrorRecord er = new(new InvalidOperationException(), "MaximumRedirectExceeded", ErrorCategory.InvalidOperation, request);
er.ErrorDetails = new ErrorDetails(WebCmdletStrings.MaximumRedirectionCountExceeded);
Expand Down Expand Up @@ -688,6 +693,33 @@ protected override void ProcessRecord()
/// </summary>
protected override void StopProcessing() => _cancelToken?.Cancel();

/// <summary>
/// Disposes the associated WebSession if it is not being used as part of a persistent session.
/// </summary>
/// <param name="disposing">True when called from Dispose() and false when called from finalizer.</param>
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing && !IsPersistentSession())
{
WebSession?.Dispose();
WebSession = null;
}

_disposed = true;
}
}

/// <summary>
/// Disposes the associated WebSession if it is not being used as part of a persistent session.
/// </summary>
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

#endregion Overrides

#region Virtual Methods
Expand Down Expand Up @@ -900,27 +932,50 @@ internal virtual void PrepareSession()
WebSession.UserAgent = UserAgent;
}

if (Proxy is not null)
// Proxy and NoProxy parameters are mutually exclusive.
// If NoProxy is provided, WebSession will turn off the proxy
// and if Proxy is provided NoProxy will be turned off.
if (NoProxy.IsPresent)
{
WebProxy webProxy = new(Proxy);
webProxy.BypassProxyOnLocal = false;
if (ProxyCredential is not null)
{
webProxy.Credentials = ProxyCredential.GetNetworkCredential();
}
else
WebSession.NoProxy = true;
}
else
{
if (Proxy is not null)
{
webProxy.UseDefaultCredentials = ProxyUseDefaultCredentials;
WebProxy webProxy = new(Proxy);
webProxy.BypassProxyOnLocal = false;
if (ProxyCredential is not null)
{
webProxy.Credentials = ProxyCredential.GetNetworkCredential();
}
else
{
webProxy.UseDefaultCredentials = ProxyUseDefaultCredentials;
}

// We don't want to update the WebSession unless the proxies are different
// as that will require us to create a new HttpClientHandler and lose connection
// persistence.
if (!webProxy.Equals(WebSession.Proxy))
{
WebSession.Proxy = webProxy;
}
}
}

WebSession.Proxy = webProxy;
if (MyInvocation.BoundParameters.ContainsKey(nameof(SslProtocol)))
{
WebSession.SslProtocol = SslProtocol;
}

if (MaximumRedirection > -1)
{
WebSession.MaximumRedirection = MaximumRedirection;
}

WebSession.SkipCertificateCheck = SkipCertificateCheck.IsPresent;

// Store the other supplied headers
if (Headers is not null)
{
Expand All @@ -945,63 +1000,20 @@ internal virtual void PrepareSession()
// Only set retry interval if retry count is set.
WebSession.RetryIntervalInSeconds = RetryIntervalSec;
}

WebSession.TimeoutSec = TimeoutSec;
}

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)
{
handler.Credentials = WebSession.Credentials;
}

if (NoProxy)
{
handler.UseProxy = false;
}
else if (WebSession.Proxy is not null)
{
handler.Proxy = WebSession.Proxy;
}
HttpClient client = WebSession.GetHttpClient(handleRedirect, out bool clientWasReset);

if (WebSession.Certificates is not null)
if (clientWasReset)
{
handler.ClientCertificates.AddRange(WebSession.Certificates);
WriteVerbose(WebCmdletStrings.WebSessionConnectionRecreated);
}

if (SkipCertificateCheck)
{
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
}

// This indicates GetResponse will handle redirects.
if (handleRedirect || WebSession.MaximumRedirection == 0)
{
handler.AllowAutoRedirect = false;
}
else if (WebSession.MaximumRedirection > 0)
{
handler.MaxAutomaticRedirections = WebSession.MaximumRedirection;
}

handler.SslProtocols = (SslProtocols)SslProtocol;

HttpClient httpClient = new(handler);

// Check timeout setting (in seconds instead of milliseconds as in HttpWebRequest)
httpClient.Timeout = TimeoutSec is 0 ? TimeSpan.FromMilliseconds(Timeout.Infinite) : new TimeSpan(0, 0, TimeoutSec);

return httpClient;
return client;
}

internal virtual HttpRequestMessage GetRequest(Uri uri)
Expand Down Expand Up @@ -1459,6 +1471,8 @@ private void ProcessAuthentication()
}
}

private bool IsPersistentSession() => MyInvocation.BoundParameters.ContainsKey(nameof(WebSession)) || MyInvocation.BoundParameters.ContainsKey(nameof(SessionVariable));

/// <summary>
/// Sets the ContentLength property of the request and writes the specified content to the request's RequestStream.
/// </summary>
Expand Down Expand Up @@ -1686,7 +1700,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<string>(fieldName) + "\"";

Expand Down Expand Up @@ -1773,7 +1787,7 @@ private static string FormatErrorMessage(string error, string contentType)
{
// Ignore errors
}

if (string.IsNullOrEmpty(formattedError))
{
// Remove HTML tags making it easier to read
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Microsoft.PowerShell.Commands
{
internal class WebProxy : IWebProxy
internal class WebProxy : IWebProxy, IEquatable<WebProxy>
{
private ICredentials _credentials;
private readonly Uri _proxyAddress;
Expand All @@ -18,6 +18,23 @@ internal WebProxy(Uri address)
_proxyAddress = address;
}

public override bool Equals(object obj) => Equals(obj as WebProxy);

public override int GetHashCode() => HashCode.Combine(_proxyAddress, _credentials, BypassProxyOnLocal);

public bool Equals(WebProxy other)
{
if (other is null)
{
return false;
}

// _proxyAddress cannot be null as it is set in the constructor
return other._credentials == _credentials
&& _proxyAddress.Equals(other._proxyAddress)
&& BypassProxyOnLocal == other.BypassProxyOnLocal;
}

public ICredentials Credentials
{
get => _credentials;
Expand Down
Loading