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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+