diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs index d0c13510c015..dc6080646700 100644 --- a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs @@ -38,11 +38,16 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeCFArrayHandle : SafeHandle { - internal SafeCFArrayHandle() + private SafeCFArrayHandle() : base(IntPtr.Zero, ownsHandle: true) { } + internal SafeCFArrayHandle(IntPtr handle, bool ownsHandle) + : base(handle, ownsHandle) + { + } + protected override bool ReleaseHandle() { Interop.CoreFoundation.CFRelease(handle); diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFDictionary.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFDictionary.cs new file mode 100644 index 000000000000..79c2979732de --- /dev/null +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFDictionary.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [DllImport(Libraries.CoreFoundationLibrary)] + internal static extern IntPtr CFDictionaryGetValue(SafeCFDictionaryHandle handle, IntPtr key); + } +} + +namespace Microsoft.Win32.SafeHandles +{ + internal sealed class SafeCFDictionaryHandle : SafeHandle + { + private SafeCFDictionaryHandle() + : base(IntPtr.Zero, ownsHandle: true) + { + } + + internal SafeCFDictionaryHandle(IntPtr handle, bool ownsHandle) + : base(handle, ownsHandle) + { + } + + protected override bool ReleaseHandle() + { + Interop.CoreFoundation.CFRelease(handle); + SetHandle(IntPtr.Zero); + return true; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + } +} diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFNumber.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFNumber.cs new file mode 100644 index 000000000000..c2bffd316dc9 --- /dev/null +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFNumber.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + internal enum CFNumberType + { + kCFNumberIntType = 9, + } + + [DllImport(Libraries.CoreFoundationLibrary)] + private static extern int CFNumberGetValue(IntPtr handle, CFNumberType type, out int value); + } +} diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFProxy.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFProxy.cs new file mode 100644 index 000000000000..1f6cc07064e5 --- /dev/null +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFProxy.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +using CFRunLoopSourceRef = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [DllImport(Libraries.CFNetworkLibrary)] + internal static extern SafeCFDictionaryHandle CFNetworkCopySystemProxySettings(); + + [DllImport(Libraries.CFNetworkLibrary)] + internal static extern SafeCFArrayHandle CFNetworkCopyProxiesForURL(SafeCreateHandle url, SafeCFDictionaryHandle proxySettings); + + internal delegate void CFProxyAutoConfigurationResultCallback(IntPtr client, IntPtr proxyList, IntPtr error); + + [DllImport(Libraries.CFNetworkLibrary)] + internal static extern CFRunLoopSourceRef CFNetworkExecuteProxyAutoConfigurationURL( + IntPtr proxyAutoConfigURL, + SafeCreateHandle targetURL, + CFProxyAutoConfigurationResultCallback cb, + ref CFStreamClientContext clientContext); + + [DllImport(Libraries.CFNetworkLibrary)] + internal static extern CFRunLoopSourceRef CFNetworkExecuteProxyAutoConfigurationScript( + IntPtr proxyAutoConfigurationScript, + SafeCreateHandle targetURL, + CFProxyAutoConfigurationResultCallback cb, + ref CFStreamClientContext clientContext); + + [StructLayout(LayoutKind.Sequential)] + internal struct CFStreamClientContext + { + public IntPtr Version; + public IntPtr Info; + public IntPtr Retain; + public IntPtr Release; + public IntPtr CopyDescription; + } + + internal class CFProxy + { + private SafeCFDictionaryHandle _dictionary; + + internal static readonly string kCFProxyTypeAutoConfigurationURL; + internal static readonly string kCFProxyTypeAutoConfigurationJavaScript; + internal static readonly string kCFProxyTypeFTP; + internal static readonly string kCFProxyTypeHTTP; + internal static readonly string kCFProxyTypeHTTPS; + internal static readonly string kCFProxyTypeSOCKS; + + private static readonly IntPtr kCFProxyAutoConfigurationJavaScriptKey; + private static readonly IntPtr kCFProxyAutoConfigurationURLKey; + private static readonly IntPtr kCFProxyHostNameKey; + private static readonly IntPtr kCFProxyPasswordKey; + private static readonly IntPtr kCFProxyPortNumberKey; + private static readonly IntPtr kCFProxyTypeKey; + private static readonly IntPtr kCFProxyUsernameKey; + + static CFProxy() + { + IntPtr lib = NativeLibrary.Load(Interop.Libraries.CFNetworkLibrary); + if (lib != IntPtr.Zero) + { + kCFProxyTypeAutoConfigurationURL = LoadCFStringSymbol(lib, "kCFProxyTypeAutoConfigurationURL"); + kCFProxyTypeAutoConfigurationJavaScript = LoadCFStringSymbol(lib, "kCFProxyTypeAutoConfigurationJavaScript"); + kCFProxyTypeFTP = LoadCFStringSymbol(lib, "kCFProxyTypeFTP"); + kCFProxyTypeHTTP = LoadCFStringSymbol(lib, "kCFProxyTypeHTTP"); + kCFProxyTypeHTTPS = LoadCFStringSymbol(lib, "kCFProxyTypeHTTPS"); + kCFProxyTypeSOCKS = LoadCFStringSymbol(lib, "kCFProxyTypeSOCKS"); + + kCFProxyAutoConfigurationJavaScriptKey = LoadSymbol(lib, "kCFProxyAutoConfigurationJavaScriptKey"); + kCFProxyAutoConfigurationURLKey = LoadSymbol(lib, "kCFProxyAutoConfigurationURLKey"); + kCFProxyHostNameKey = LoadSymbol(lib, "kCFProxyHostNameKey"); + kCFProxyPasswordKey = LoadSymbol(lib, "kCFProxyPasswordKey"); + kCFProxyPortNumberKey = LoadSymbol(lib, "kCFProxyPortNumberKey"); + kCFProxyTypeKey = LoadSymbol(lib, "kCFProxyTypeKey"); + kCFProxyUsernameKey = LoadSymbol(lib, "kCFProxyUsernameKey"); + } + } + + public CFProxy(SafeCFDictionaryHandle dictionary) + { + _dictionary = dictionary; + } + + private static IntPtr LoadSymbol(IntPtr lib, string name) + { + IntPtr indirect = NativeLibrary.GetExport(lib, name); + return indirect == IntPtr.Zero ? IntPtr.Zero : Marshal.ReadIntPtr(indirect); + } + + private static string LoadCFStringSymbol(IntPtr lib, string name) + { + using (SafeCFStringHandle cfString = new SafeCFStringHandle(LoadSymbol(lib, name), false)) + { + Debug.Assert(!cfString.IsInvalid); + return Interop.CoreFoundation.CFStringToString(cfString); + } + } + + private string GetString(IntPtr key) + { + IntPtr dictValue = CFDictionaryGetValue(_dictionary, key); + if (dictValue != IntPtr.Zero) + { + using (SafeCFStringHandle handle = new SafeCFStringHandle(dictValue, false)) + { + return CFStringToString(handle); + } + } + return null; + } + + public string ProxyType => GetString(kCFProxyTypeKey); + public string HostName => GetString(kCFProxyHostNameKey); + public string Username => GetString(kCFProxyUsernameKey); + public string Password => GetString(kCFProxyPasswordKey); + + public int PortNumber + { + get + { + IntPtr dictValue = CFDictionaryGetValue(_dictionary, kCFProxyPortNumberKey); + if (dictValue != IntPtr.Zero && CFNumberGetValue(dictValue, CFNumberType.kCFNumberIntType, out int value) > 0) + { + return value; + } + return -1; + } + } + + public IntPtr AutoConfigurationURL => CFDictionaryGetValue(_dictionary, kCFProxyAutoConfigurationURLKey); + public IntPtr AutoConfigurationJavaScript => CFDictionaryGetValue(_dictionary, kCFProxyAutoConfigurationJavaScriptKey); + } + } +} diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs index aa7a15054cc5..17fb307d32d1 100644 --- a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs @@ -91,6 +91,11 @@ internal SafeCFStringHandle() { } + internal SafeCFStringHandle(IntPtr handle, bool ownsHandle) + : base(handle, ownsHandle) + { + } + protected override bool ReleaseHandle() { Interop.CoreFoundation.CFRelease(handle); diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFUrl.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFUrl.cs new file mode 100644 index 000000000000..88c2bbd0ac5b --- /dev/null +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFUrl.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [DllImport(Libraries.CoreFoundationLibrary)] + private static extern SafeCreateHandle CFURLCreateWithString( + IntPtr allocator, + SafeCreateHandle str, + IntPtr baseUrl); + + internal static SafeCreateHandle CFURLCreateWithString(string url) + { + Debug.Assert(url != null); + using (SafeCreateHandle stringHandle = CFStringCreateWithCString(url)) + { + return CFURLCreateWithString(IntPtr.Zero, stringHandle, IntPtr.Zero); + } + } + } +} diff --git a/src/Common/src/Interop/OSX/Interop.Libraries.cs b/src/Common/src/Interop/OSX/Interop.Libraries.cs index 89f6a3f53755..ffac0b25dcde 100644 --- a/src/Common/src/Interop/OSX/Interop.Libraries.cs +++ b/src/Common/src/Interop/OSX/Interop.Libraries.cs @@ -7,7 +7,8 @@ internal static partial class Interop internal static partial class Libraries { internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; - internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices"; + internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices"; + internal const string CFNetworkLibrary = "/System/Library/Frameworks/CFNetwork.framework/CFNetwork"; internal const string libproc = "libproc"; internal const string LibSystemCommonCrypto = "/usr/lib/system/libcommonCrypto"; internal const string LibSystemKernel = "/usr/lib/system/libsystem_kernel"; diff --git a/src/Common/src/Interop/OSX/Interop.RunLoop.cs b/src/Common/src/Interop/OSX/Interop.RunLoop.cs index 9257952b4499..f7c33442662c 100644 --- a/src/Common/src/Interop/OSX/Interop.RunLoop.cs +++ b/src/Common/src/Interop/OSX/Interop.RunLoop.cs @@ -34,6 +34,12 @@ internal static partial class RunLoop #endif internal extern static void CFRunLoopRun(); + /// + /// Runs the current thread’s CFRunLoop object in a particular mode. + /// + [DllImport(Interop.Libraries.CoreFoundationLibrary)] + internal extern static int CFRunLoopRunInMode(CFStringRef mode, double seconds, int returnAfterSourceHandled); + /// /// Notifies a RunLoop to stop and return control to the execution context that called CFRunLoopRun /// @@ -66,7 +72,14 @@ internal static partial class RunLoop /// The run loop mode of rl from which to remove source. [DllImport(Interop.Libraries.CoreFoundationLibrary)] internal static extern void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode); - + + /// + /// Invalidates a CFRunLoopSource object, stopping it from ever firing again. + /// + /// The run loop source to invalidate. + [DllImport(Interop.Libraries.CoreFoundationLibrary)] + internal static extern void CFRunLoopSourceInvalidate(CFRunLoopSourceRef source); + /// /// Returns a bool that indicates whether the run loop is waiting for an event. /// diff --git a/src/System.Net.Http/src/System.Net.Http.csproj b/src/System.Net.Http/src/System.Net.Http.csproj index 9b800cfd5f26..cf5336a230fd 100644 --- a/src/System.Net.Http/src/System.Net.Http.csproj +++ b/src/System.Net.Http/src/System.Net.Http.csproj @@ -214,7 +214,6 @@ - Common\System\Net\ContextAwareResult.Unix.cs @@ -256,6 +255,49 @@ Common\System\Net\Security\Unix\SafeDeleteNegoContext.cs + + + + + + + + Common\Interop\OSX\Interop.CoreFoundation.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFArray.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFData.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFDictionary.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFProxy.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFUrl.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFString.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFString.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFError.cs + + + Common\Interop\OSX\Interop.RunLoop.cs + + + Common\Interop\OSX\Interop.Libraries.cs + + + Common\Microsoft\Win32\SafeHandles\SafeCreateHandle.OSX.cs + + diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MacProxy.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MacProxy.cs new file mode 100644 index 000000000000..dfd5b31bbd87 --- /dev/null +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MacProxy.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Net.Http; +using System.Net; +using System.Collections.Generic; +using Microsoft.Win32.SafeHandles; +using static Interop.CoreFoundation; +using static Interop.RunLoop; + +using CFRunLoopRef = System.IntPtr; +using CFRunLoopSourceRef = System.IntPtr; + +namespace System.Net.Http +{ + internal sealed class MacProxy : IWebProxy + { + public ICredentials Credentials + { + get => null; + set => throw new NotSupportedException(); + } + + private static Uri GetProxyUri(string scheme, CFProxy proxy) + { + var uriBuilder = new UriBuilder( + scheme, + proxy.HostName, + proxy.PortNumber); + + // TODO: Issue #26593 - Credentials are not propagated + + return uriBuilder.Uri; + } + + public Uri ExecuteProxyAutoConfiguration(SafeCreateHandle cfurl, CFProxy proxy) + { + Uri result = null; + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + + // Callback that will be called after executing the configuration script + CFProxyAutoConfigurationResultCallback cb = (IntPtr client, IntPtr proxyListHandle, IntPtr error) => + { + if (proxyListHandle != IntPtr.Zero) + { + using (var proxyList = new SafeCFArrayHandle(proxyListHandle, false)) + { + long proxyCount = CFArrayGetCount(proxyList); + for (int i = 0; i < proxyCount; i++) + { + IntPtr proxyValue = CFArrayGetValueAtIndex(proxyList, i); + using (SafeCFDictionaryHandle proxyDict = new SafeCFDictionaryHandle(proxyValue, false)) + { + CFProxy proxy = new CFProxy(proxyDict); + if (proxy.ProxyType == CFProxy.kCFProxyTypeHTTP || proxy.ProxyType == CFProxy.kCFProxyTypeHTTPS) + { + result = GetProxyUri("http", proxy); + break; + } + } + } + } + } + CFRunLoopStop(runLoop); + }; + + var clientContext = new CFStreamClientContext(); + CFRunLoopSourceRef loopSource = + proxy.ProxyType == CFProxy.kCFProxyTypeAutoConfigurationURL ? + CFNetworkExecuteProxyAutoConfigurationURL(proxy.AutoConfigurationURL, cfurl, cb, ref clientContext) : + CFNetworkExecuteProxyAutoConfigurationScript(proxy.AutoConfigurationJavaScript, cfurl, cb, ref clientContext); + + using (var mode = CFStringCreateWithCString(typeof(MacProxy).FullName)) + { + IntPtr modeHandle = mode.DangerousGetHandle(); + CFRunLoopAddSource(runLoop, loopSource, modeHandle); + CFRunLoopRunInMode(modeHandle, double.MaxValue, 0); + CFRunLoopSourceInvalidate(loopSource); + } + + GC.KeepAlive(cb); + + return result; + } + + public Uri GetProxy(Uri targetUri) + { + using (SafeCFDictionaryHandle systemProxySettings = CFNetworkCopySystemProxySettings()) + using (SafeCreateHandle cfurl = CFURLCreateWithString(targetUri.AbsoluteUri)) + using (SafeCFArrayHandle proxies = CFNetworkCopyProxiesForURL(cfurl, systemProxySettings)) + { + long proxyCount = CFArrayGetCount(proxies); + for (int i = 0; i < proxyCount; i++) + { + IntPtr proxyValue = CFArrayGetValueAtIndex(proxies, i); + using (SafeCFDictionaryHandle proxyDict = new SafeCFDictionaryHandle(proxyValue, false)) + { + CFProxy proxy = new CFProxy(proxyDict); + + if (proxy.ProxyType == CFProxy.kCFProxyTypeAutoConfigurationURL || proxy.ProxyType == CFProxy.kCFProxyTypeAutoConfigurationJavaScript) + { + Uri result = ExecuteProxyAutoConfiguration(cfurl, proxy); + if (result != null) + return result; + } + else if (proxy.ProxyType == CFProxy.kCFProxyTypeHTTP || proxy.ProxyType == CFProxy.kCFProxyTypeHTTPS) + { + return GetProxyUri("http", proxy); + } + } + } + } + + return null; + } + + public bool IsBypassed(Uri targetUri) + { + if (targetUri == null) + throw new ArgumentNullException(nameof(targetUri)); + + Uri proxyUri = GetProxy(targetUri); + return Equals(proxyUri, targetUri) || proxyUri == null; + } + } +} diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SystemProxyInfo.OSX.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SystemProxyInfo.OSX.cs new file mode 100644 index 000000000000..2d143dd0183d --- /dev/null +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SystemProxyInfo.OSX.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Http +{ + internal static class SystemProxyInfo + { + // On Unix we get default proxy configuration from environment variables + public static IWebProxy ConstructSystemProxy() + { + return HttpEnvironmentProxy.TryCreate(out IWebProxy proxy) ? proxy : new MacProxy(); + } + } +} +