diff --git a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFNumber.cs b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFNumber.cs index 22d51bdd11cc36..50089508f7ee82 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFNumber.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFNumber.cs @@ -7,6 +7,9 @@ using System.Text; using Microsoft.Win32.SafeHandles; +// Declared as signed long, which has sizeof(void*) on OSX. +using CFIndex = System.IntPtr; + internal static partial class Interop { internal static partial class CoreFoundation @@ -16,7 +19,14 @@ internal enum CFNumberType kCFNumberIntType = 9, } - [LibraryImport(Libraries.CoreFoundationLibrary)] - private static unsafe partial int CFNumberGetValue(IntPtr handle, CFNumberType type, int* value); + // CFNumberType is declared as CF_ENUM(CFIndex, CFNumberType), so it has to be passed as a CFIndex. + // Passing the int-sized managed enum leaves the upper bits of the argument register unspecified. + [LibraryImport(Libraries.CoreFoundationLibrary, EntryPoint = "CFNumberGetValue")] + private static unsafe partial int _CFNumberGetValue(IntPtr handle, CFIndex type, int* value); + + private static unsafe int CFNumberGetValue(IntPtr handle, CFNumberType type, int* value) + { + return _CFNumberGetValue(handle, (CFIndex)type, value); + } } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MacProxy.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MacProxy.cs index 05cef8a22315ac..6dc04862f5ffac 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MacProxy.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MacProxy.cs @@ -82,8 +82,16 @@ private static Uri GetProxyUri(string scheme, CFProxy proxy) public Uri? GetProxy(Uri targetUri) { using (SafeCFDictionaryHandle systemProxySettings = CFNetworkCopySystemProxySettings()) + { + return GetProxy(targetUri, systemProxySettings); + } + } + + // Resolves the proxy for targetUri from proxy settings in the format returned by CFNetworkCopySystemProxySettings. + internal static Uri? GetProxy(Uri targetUri, SafeCFDictionaryHandle proxySettings) + { using (SafeCreateHandle cfurl = CFURLCreateWithString(targetUri.AbsoluteUri)) - using (SafeCFArrayHandle proxies = CFNetworkCopyProxiesForURL(cfurl, systemProxySettings)) + using (SafeCFArrayHandle proxies = CFNetworkCopyProxiesForURL(cfurl, proxySettings)) { long proxyCount = CFArrayGetCount(proxies); for (int i = 0; i < proxyCount; i++) diff --git a/src/libraries/System.Net.Http/tests/UnitTests/Fakes/MacProxy.cs b/src/libraries/System.Net.Http/tests/UnitTests/Fakes/MacProxy.cs deleted file mode 100644 index ec832f3303a2b3..00000000000000 --- a/src/libraries/System.Net.Http/tests/UnitTests/Fakes/MacProxy.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Net.Http -{ - internal sealed class MacProxy : IWebProxy - { - public ICredentials Credentials - { - get => throw NotImplemented.ByDesignWithMessage("Mac Proxy not implemented"); - set => throw NotImplemented.ByDesignWithMessage("Mac Proxy not implemented"); - } - - public Uri GetProxy(Uri targetUri) - { - throw NotImplemented.ByDesignWithMessage("Mac Proxy not implemented"); - } - - public bool IsBypassed(Uri targetUri) - { - throw NotImplemented.ByDesignWithMessage("Mac Proxy not implemented"); - } - } -} diff --git a/src/libraries/System.Net.Http/tests/UnitTests/MacProxyTest.cs b/src/libraries/System.Net.Http/tests/UnitTests/MacProxyTest.cs new file mode 100644 index 00000000000000..de15e1628bb9eb --- /dev/null +++ b/src/libraries/System.Net.Http/tests/UnitTests/MacProxyTest.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using Xunit; + +namespace System.Net.Http.Tests +{ + // Runs MacProxy against proxy settings created in-process, in the format returned by + // CFNetworkCopySystemProxySettings, so the tests don't depend on the machine's proxy configuration. + // The tests that return a proxy read its port with CFNumberGetValue, whose CFNumberType parameter is + // CFIndex-sized. Passing it as a 32-bit value crashed on the CoreCLR interpreter (iOS, tvOS, Mac Catalyst). + public partial class MacProxyTest + { + private static readonly Uri s_targetUri = new Uri("http://www.example.com/"); + private static readonly Uri s_proxyUri = new Uri("http://127.0.0.1:8888/"); + + [Fact] + public void GetProxy_HttpProxy_ReturnsProxyUri() + { + using SafeCFDictionaryHandle settings = CreateProxySettings( + ("kCFNetworkProxiesHTTPEnable", CreateNumber(1)), + ("kCFNetworkProxiesHTTPProxy", Interop.CoreFoundation.CFStringCreateWithCString("127.0.0.1")), + ("kCFNetworkProxiesHTTPPort", CreateNumber(8888))); + + Assert.Equal(s_proxyUri, MacProxy.GetProxy(s_targetUri, settings)); + } + + [Fact] + public void GetProxy_ProxyAutoConfigurationScript_ReturnsProxyUri() + { + using SafeCFDictionaryHandle settings = CreateProxySettings( + ("kCFNetworkProxiesProxyAutoConfigEnable", CreateNumber(1)), + ("kCFNetworkProxiesProxyAutoConfigJavaScript", Interop.CoreFoundation.CFStringCreateWithCString( + "function FindProxyForURL(url, host) { return \"PROXY 127.0.0.1:8888\"; }"))); + + Assert.Equal(s_proxyUri, MacProxy.GetProxy(s_targetUri, settings)); + } + + [Fact] + public void GetProxy_ProxyAutoConfigurationScriptReturnsDirect_ReturnsNull() + { + using SafeCFDictionaryHandle settings = CreateProxySettings( + ("kCFNetworkProxiesProxyAutoConfigEnable", CreateNumber(1)), + ("kCFNetworkProxiesProxyAutoConfigJavaScript", Interop.CoreFoundation.CFStringCreateWithCString( + "function FindProxyForURL(url, host) { return \"DIRECT\"; }"))); + + Assert.Null(MacProxy.GetProxy(s_targetUri, settings)); + } + + [Fact] + public void GetProxy_NoProxy_ReturnsNull() + { + using SafeCFDictionaryHandle settings = CreateProxySettings(); + + Assert.Null(MacProxy.GetProxy(s_targetUri, settings)); + } + + private static unsafe SafeCreateHandle CreateNumber(int value) => + CFNumberCreate(IntPtr.Zero, kCFNumberIntType, &value); + + // Keys are the names of CFStringRef constants exported by CFNetwork. The values are released once the dictionary has retained them. + private static unsafe SafeCFDictionaryHandle CreateProxySettings(params (string Key, SafeCreateHandle Value)[] settings) + { + try + { + IntPtr cfNetwork = NativeLibrary.Load(Interop.Libraries.CFNetworkLibrary); + IntPtr coreFoundation = NativeLibrary.Load(Interop.Libraries.CoreFoundationLibrary); + + IntPtr[] keys = new IntPtr[settings.Length]; + IntPtr[] values = new IntPtr[settings.Length]; + for (int i = 0; i < settings.Length; i++) + { + keys[i] = Marshal.ReadIntPtr(NativeLibrary.GetExport(cfNetwork, settings[i].Key)); + values[i] = settings[i].Value.DangerousGetHandle(); + } + + fixed (IntPtr* pKeys = keys) + fixed (IntPtr* pValues = values) + { + return CFDictionaryCreate(IntPtr.Zero, pKeys, pValues, settings.Length, + NativeLibrary.GetExport(coreFoundation, "kCFTypeDictionaryKeyCallBacks"), + NativeLibrary.GetExport(coreFoundation, "kCFTypeDictionaryValueCallBacks")); + } + } + finally + { + foreach ((_, SafeCreateHandle value) in settings) + { + value.Dispose(); + } + } + } + + // CFNumberType is declared as CF_ENUM(CFIndex, CFNumberType). + private const nint kCFNumberIntType = 9; + + [LibraryImport(Interop.Libraries.CoreFoundationLibrary)] + private static unsafe partial SafeCreateHandle CFNumberCreate(IntPtr allocator, nint theType, int* valuePtr); + + [LibraryImport(Interop.Libraries.CoreFoundationLibrary)] + private static unsafe partial SafeCFDictionaryHandle CFDictionaryCreate( + IntPtr allocator, IntPtr* keys, IntPtr* values, nint numValues, IntPtr keyCallBacks, IntPtr valueCallBacks); + } +} + +internal static partial class Interop +{ + // Interop\OSX\Interop.Libraries.cs can't be compiled next to Interop\Windows\Interop.Libraries.cs + // (both define MsQuic), so define the libraries that the MacProxy interop uses. + internal static partial class Libraries + { + internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; + internal const string CFNetworkLibrary = "/System/Library/Frameworks/CFNetwork.framework/CFNetwork"; + } +} diff --git a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj index 43167dc48c6007..bacf276602d91b 100755 --- a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj @@ -259,7 +259,6 @@ - @@ -430,4 +429,29 @@ + + + + + + + + + + + + + +