Skip to content

Commit

Permalink
Add CoreFoundation interop code
Browse files Browse the repository at this point in the history
  • Loading branch information
xoofx committed Dec 16, 2024
1 parent e0e2d66 commit 2b73666
Show file tree
Hide file tree
Showing 14 changed files with 881 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/Ultra.Core/Interop/Interop.CoreFoundation.CFArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

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
{
[LibraryImport(Libraries.CoreFoundationLibrary, EntryPoint = "CFArrayGetCount")]
private static partial CFIndex _CFArrayGetCount(SafeCFArrayHandle cfArray);

// Follows the "Get" version of the "Create" rule, so needs to return an IntPtr to
// prevent CFRelease from being called on the SafeHandle close.
[LibraryImport(Libraries.CoreFoundationLibrary, EntryPoint = "CFArrayGetValueAtIndex")]
private static partial IntPtr CFArrayGetValueAtIndex(SafeCFArrayHandle cfArray, CFIndex index);

internal static long CFArrayGetCount(SafeCFArrayHandle cfArray)
{
return _CFArrayGetCount(cfArray).ToInt64();
}

internal static IntPtr CFArrayGetValueAtIndex(SafeCFArrayHandle cfArray, int index)
{
return CFArrayGetValueAtIndex(cfArray, new CFIndex(index));
}
}
}
57 changes: 57 additions & 0 deletions src/Ultra.Core/Interop/Interop.CoreFoundation.CFData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

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
{
[LibraryImport(Libraries.CoreFoundationLibrary)]
private static unsafe partial byte* CFDataGetBytePtr(SafeCFDataHandle cfData);

[LibraryImport(Libraries.CoreFoundationLibrary)]
private static partial CFIndex CFDataGetLength(SafeCFDataHandle cfData);

internal static unsafe Span<byte> CFDataDangerousGetSpan(SafeCFDataHandle cfData)
{
long length = CFDataGetLength(cfData).ToInt64();
byte* dataBytes = CFDataGetBytePtr(cfData);
return new Span<byte>(dataBytes, checked((int)length));
}

internal static byte[] CFGetData(SafeCFDataHandle cfData)
{
return CFDataDangerousGetSpan(cfData).ToArray();
}

internal static unsafe bool TryCFWriteData(SafeCFDataHandle cfData, Span<byte> destination, out int bytesWritten)
{
long length = CFDataGetLength(cfData).ToInt64();

if (length > 0)
{
if (destination.Length < length)
{
bytesWritten = 0;
return false;
}

byte* dataBytes = CFDataGetBytePtr(cfData);
fixed (byte* destinationPtr = &MemoryMarshal.GetReference(destination))
{
Buffer.MemoryCopy(dataBytes, destinationPtr, destination.Length, length);
}
}

bytesWritten = (int)length;
return true;
}
}
}
46 changes: 46 additions & 0 deletions src/Ultra.Core/Interop/Interop.CoreFoundation.CFDate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

using Microsoft.Win32.SafeHandles;

#pragma warning disable SA1121 // we don't want to simplify built-ins here as we're using aliasing
using CFAbsoluteTime = System.Double;

internal static partial class Interop
{
internal static partial class CoreFoundation
{
// https://developer.apple.com/reference/corefoundation/cfabsolutetime
private static readonly DateTime s_cfDateEpoch = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc);

[LibraryImport(Libraries.CoreFoundationLibrary)]
private static partial SafeCFDateHandle CFDateCreate(IntPtr zero, CFAbsoluteTime at);

internal static SafeCFDateHandle CFDateCreate(DateTime date)
{
Debug.Assert(
date.Kind != DateTimeKind.Unspecified,
"DateTimeKind.Unspecified should be specified to Local or UTC by the caller");

// UTC stays unchanged, Local is changed.
// Unspecified gets treated as Local (which may or may not be desired).
DateTime utcDate = date.ToUniversalTime();

double epochDeltaSeconds = (utcDate - s_cfDateEpoch).TotalSeconds;

SafeCFDateHandle cfDate = CFDateCreate(IntPtr.Zero, epochDeltaSeconds);

if (cfDate.IsInvalid)
{
cfDate.Dispose();
throw new OutOfMemoryException();
}

return cfDate;
}
}
}
17 changes: 17 additions & 0 deletions src/Ultra.Core/Interop/Interop.CoreFoundation.CFDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

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
{
[LibraryImport(Libraries.CoreFoundationLibrary)]
internal static partial IntPtr CFDictionaryGetValue(SafeCFDictionaryHandle handle, IntPtr key);
}
}
42 changes: 42 additions & 0 deletions src/Ultra.Core/Interop/Interop.CoreFoundation.CFError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

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
{
[LibraryImport(Libraries.CoreFoundationLibrary)]
private static partial CFIndex CFErrorGetCode(SafeCFErrorHandle cfError);

[LibraryImport(Libraries.CoreFoundationLibrary)]
private static partial SafeCFStringHandle CFErrorCopyDescription(SafeCFErrorHandle cfError);

internal static int GetErrorCode(SafeCFErrorHandle cfError)
{
unchecked
{
return (int)(CFErrorGetCode(cfError).ToInt64());
}
}

internal static string? GetErrorDescription(SafeCFErrorHandle cfError)
{
if (cfError.IsInvalid)
{
return null;
}

using SafeCFStringHandle cfString = CFErrorCopyDescription(cfError);
return CFStringToString(cfString);
}
}
}
22 changes: 22 additions & 0 deletions src/Ultra.Core/Interop/Interop.CoreFoundation.CFNumber.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

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,
}

[LibraryImport(Libraries.CoreFoundationLibrary)]
private static unsafe partial int CFNumberGetValue(IntPtr handle, CFNumberType type, int* value);
}
}
150 changes: 150 additions & 0 deletions src/Ultra.Core/Interop/Interop.CoreFoundation.CFProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

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
{
[LibraryImport(Libraries.CFNetworkLibrary)]
internal static partial SafeCFDictionaryHandle CFNetworkCopySystemProxySettings();

[LibraryImport(Libraries.CFNetworkLibrary)]
internal static partial SafeCFArrayHandle CFNetworkCopyProxiesForURL(SafeCreateHandle url, SafeCFDictionaryHandle proxySettings);

internal delegate void CFProxyAutoConfigurationResultCallback(IntPtr client, IntPtr proxyList, IntPtr error);

[LibraryImport(Libraries.CFNetworkLibrary)]
internal static partial CFRunLoopSourceRef CFNetworkExecuteProxyAutoConfigurationURL(
IntPtr proxyAutoConfigURL,
SafeCreateHandle targetURL,
CFProxyAutoConfigurationResultCallback cb,
ref CFStreamClientContext clientContext);

[LibraryImport(Libraries.CFNetworkLibrary)]
internal static partial 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 sealed 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;

#pragma warning disable CA1810 // explicit static cctor
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");
}
}
#pragma warning restore CA1810

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)))
{
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))
{
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);
unsafe
{
int value;
if (dictValue != IntPtr.Zero && CFNumberGetValue(dictValue, CFNumberType.kCFNumberIntType, &value) > 0)
{
return value;
}
}
return -1;
}
}

public IntPtr AutoConfigurationURL => CFDictionaryGetValue(_dictionary, kCFProxyAutoConfigurationURLKey);
public IntPtr AutoConfigurationJavaScript => CFDictionaryGetValue(_dictionary, kCFProxyAutoConfigurationJavaScriptKey);
}
}
}
Loading

0 comments on commit 2b73666

Please sign in to comment.