[d16-3] [CFNetwork] Expose methods that were ignored until the dependencies were present. (#6668)

We can know expose the methods. Bindings had to be updated considering
the fact that we have a scrut and the type of callbacks.

Fixes: https://github.com/xamarin/xamarin-macios/issues/6195
This commit is contained in:
monojenkins 2019-07-29 05:23:33 -04:00 коммит произвёл Manuel de la Pena
Родитель 31467d21be
Коммит 582ac0ac78
5 изменённых файлов: 459 добавлений и 40 удалений

Просмотреть файл

@ -30,6 +30,8 @@
using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using ObjCRuntime;
using Foundation;
@ -579,62 +581,259 @@ namespace CoreFoundation {
dict.DangerousRelease ();
return new CFProxySettings (dict);
}
#if notyet
// FIXME: These require bindings for CFRunLoopSource and CFStreamClientContext
public delegate void CFProxyAutoConfigurationResultCallback (IntPtr client, NSArray proxyList, NSError error);
delegate void CFProxyAutoConfigurationResultCallbackInternal (IntPtr client, IntPtr proxyList, IntPtr error);
// helper delegate to reuse code
delegate IntPtr CreatePACCFRunLoopSource (CFProxyAutoConfigurationResultCallbackInternal cb, ref CFStreamClientContext context);
static CFProxy[] ParseProxies (IntPtr proxyList)
{
CFProxy[] proxies = null;
if (proxyList != IntPtr.Zero) {
// it was retained in the cbs.
using (var array = new CFArray (proxyList, false)) {
proxies = new CFProxy [array.Count];
for (int i = 0; i < proxies.Length; i++) {
var dict = Runtime.GetNSObject<NSDictionary> (array.GetValue (i));
proxies[i] = new CFProxy (dict);
}
}
}
return proxies;
}
// helper struct to contain all the data that was used in the callback
struct PACProxyCallbackData
{
public IntPtr ProxyListPtr; // Pointer to a CFArray to later be parsed
public IntPtr ErrorPtr; // Pointer to the Error
public IntPtr CFRunLoopPtr; // Pointer to the runloop, needed to be stopped
public CFProxy [] ProxyList {
get {
if (ProxyListPtr != IntPtr.Zero)
return ParseProxies (ProxyListPtr);
return null;
}
}
public NSError Error {
get {
if (ErrorPtr != IntPtr.Zero)
return Runtime.GetNSObject<NSError> (ErrorPtr);
return null;
}
}
}
// callback that will sent the client info
[MonoPInvokeCallback (typeof (CFProxyAutoConfigurationResultCallbackInternal))]
static void ExecutePacCallback (IntPtr client, IntPtr proxyList, IntPtr error)
{
// grab the required structure and set the data, according apple docs:
// client
// The client reference originally passed in the clientContext parameter of the
// CFNetworkExecuteProxyAutoConfigurationScript or CFNetworkExecuteProxyAutoConfigurationURL call
// that triggered this callback.
// Well, that is NOT TRUE, the client passed is the client.Info pointer not the client.
var pacCbData = (PACProxyCallbackData) Marshal.PtrToStructure (client, typeof (PACProxyCallbackData));
// make sure is not released, will be released by the parsing method.
if (proxyList != IntPtr.Zero) {
CFObject.CFRetain (proxyList);
pacCbData.ProxyListPtr = proxyList;
}
if (error != IntPtr.Zero) {
NSObject.DangerousRetain (error);
pacCbData.ErrorPtr = error;
}
// stop the CFRunLoop
var runLoop = new CFRunLoop (pacCbData.CFRunLoopPtr);
Marshal.StructureToPtr (pacCbData, client, false);
runLoop.Stop ();
}
static async Task<(CFProxy[] proxies, NSError error)> ExecutePacCFRunLoopSourceAsync (CreatePACCFRunLoopSource factory, CancellationToken cancellationToken)
{
CFProxy[] proxies = null;
NSError outError = null;
if (cancellationToken.IsCancellationRequested)
throw new OperationCanceledException ("Operation was cancelled.");
await Task.Run (() => {
// we need the runloop of THIS thread, so it is important to get it in the correct context
var runLoop = CFRunLoop.Current;
// build a struct that will have all the needed info for the callback
var pacCbData = new PACProxyCallbackData ();
pacCbData.CFRunLoopPtr = runLoop.Handle;
var pacDataPtr = Marshal.AllocHGlobal (Marshal.SizeOf (pacCbData));
try {
Marshal.StructureToPtr (pacCbData, pacDataPtr, false);
var clientContext = new CFStreamClientContext ();
clientContext.Info = pacDataPtr;
using (var loopSource = new CFRunLoopSource (factory (ExecutePacCallback, ref clientContext)))
using (var mode = new NSString ("Xamarin.iOS.Proxy")) {
if (cancellationToken.IsCancellationRequested)
throw new OperationCanceledException ("Operation was cancelled.");
cancellationToken.Register (() => {
//if user cancels, we invalidte the source, stop the runloop and remove the source
loopSource.Invalidate ();
runLoop.RemoveSource (loopSource, mode);
runLoop.Stop ();
});
runLoop.AddSource (loopSource, mode);
// blocks until stop is called, will be done in the cb set previously
runLoop.RunInMode (mode, double.MaxValue, false);
// does not raise an error if source is not longer present, so no need to worry
runLoop.RemoveSource (loopSource, mode);
}
if (cancellationToken.IsCancellationRequested)
throw new OperationCanceledException ("Operation was cancelled.");
pacCbData = (PACProxyCallbackData) Marshal.PtrToStructure (pacDataPtr, typeof (PACProxyCallbackData));
// get data from the struct
proxies = pacCbData.ProxyList;
outError = pacCbData.Error;
} finally {
// clean resources
if (pacCbData.ProxyListPtr != IntPtr.Zero)
CFObject.CFRelease (pacCbData.ProxyListPtr);
if (pacCbData.ErrorPtr != IntPtr.Zero)
NSObject.DangerousRelease (pacCbData.ErrorPtr);
Marshal.FreeHGlobal (pacDataPtr);
}
}, cancellationToken).ConfigureAwait (false);
if (cancellationToken.IsCancellationRequested)
throw new OperationCanceledException ("Operation was cancelled.");
return (proxies: proxies, error: outError);
}
static CFProxy[] ExecutePacCFRunLoopSourceBlocking (CreatePACCFRunLoopSource factory, out NSError outError)
{
var runLoop = CFRunLoop.Current;
outError = null;
// build a struct that will have all the needed info for the callback
var pacCbData = new PACProxyCallbackData ();
pacCbData.CFRunLoopPtr = runLoop.Handle;
var pacDataPtr = Marshal.AllocHGlobal (Marshal.SizeOf (pacCbData));
try {
Marshal.StructureToPtr (pacCbData, pacDataPtr, false);
var clientContext = new CFStreamClientContext ();
clientContext.Info = pacDataPtr;
using (var loopSource = new CFRunLoopSource (factory (ExecutePacCallback, ref clientContext)))
using (var mode = new NSString ("Xamarin.iOS.Proxy")) {
runLoop.AddSource (loopSource, mode);
runLoop.RunInMode (mode, double.MaxValue, false);
runLoop.RemoveSource (loopSource, mode);
}
pacCbData = (PACProxyCallbackData) Marshal.PtrToStructure (pacDataPtr, typeof (PACProxyCallbackData));
// get data from the struct
outError = pacCbData.Error;
return pacCbData.ProxyList;
} finally {
if (pacCbData.ProxyListPtr != IntPtr.Zero)
CFObject.CFRelease (pacCbData.ProxyListPtr);
if (pacCbData.ErrorPtr != IntPtr.Zero)
NSObject.DangerousRelease (pacCbData.ErrorPtr);
Marshal.FreeHGlobal (pacDataPtr);
}
}
[DllImport (Constants.CFNetworkLibrary)]
extern static /* CFRunLoopSourceRef __nonnull */ IntPtr CFNetworkExecuteProxyAutoConfigurationScript (
/* CFStringRef __nonnull */ IntPtr proxyAutoConfigurationScript,
/* CFURLRef __nonnull */ IntPtr targetURL,
/* CFProxyAutoConfigurationResultCallback __nonnull */ IntPtr cb,
/* CFStreamClientContext * __nonnull */ IntPtr clientContext);
public static CFRunLoopSource ExecuteProxyAutoConfigurationScript (NSString proxyAutoConfigurationScript, NSUrl targetURL, CFProxyAutoConfigurationResultCallback resultCallback, CFStreamClientContext clientContext)
/* CFProxyAutoConfigurationResultCallback __nonnull */ CFProxyAutoConfigurationResultCallbackInternal cb,
/* CFStreamClientContext * __nonnull */ ref CFStreamClientContext clientContext);
public static CFProxy[] ExecuteProxyAutoConfigurationScript (string proxyAutoConfigurationScript, Uri targetUrl, out NSError outError)
{
outError = null;
if (proxyAutoConfigurationScript == null)
throw new ArgumentNullException ("proxyAutoConfigurationScript");
throw new ArgumentNullException (nameof (proxyAutoConfigurationScript));
if (targetURL == null)
throw new ArgumentNullException ("targetURL");
if (targetUrl == null)
throw new ArgumentNullException (nameof (targetUrl));
if (resultCallback == null)
throw new ArgumentNullException ("resultCallback");
if (clientContext == null)
throw new ArgumentNullException ("clientContext");
IntPtr source = CFNetworkExecuteProxyAutoConfigurationScript (proxyAutoConfigurationScript.Handle, targetURL.Handle, resultCallback, clientContext);
return (source == IntPtr.Zero) ? null : new CFRunLoopSource (source);
using (var pacScript = new NSString (proxyAutoConfigurationScript))
using (var url = new NSUrl (targetUrl.AbsoluteUri)) {
CreatePACCFRunLoopSource factory = delegate (CFProxyAutoConfigurationResultCallbackInternal cb, ref CFStreamClientContext context) {
return CFNetworkExecuteProxyAutoConfigurationScript (pacScript.Handle, url.Handle, cb, ref context);
};
return ExecutePacCFRunLoopSourceBlocking (factory, out outError);
}
}
public static async Task<(CFProxy[] proxies, NSError error)> ExecuteProxyAutoConfigurationScriptAsync (string proxyAutoConfigurationScript, Uri targetUrl, CancellationToken cancellationToken)
{
if (proxyAutoConfigurationScript == null)
throw new ArgumentNullException (nameof (proxyAutoConfigurationScript));
if (targetUrl == null)
throw new ArgumentNullException (nameof (targetUrl));
using (var pacScript = new NSString (proxyAutoConfigurationScript))
using (var url = new NSUrl (targetUrl.AbsoluteUri)) {
CreatePACCFRunLoopSource factory = delegate (CFProxyAutoConfigurationResultCallbackInternal cb, ref CFStreamClientContext context) {
return CFNetworkExecuteProxyAutoConfigurationScript (pacScript.Handle, url.Handle, cb, ref context);
};
// use the helper task with a factory for this method
return await ExecutePacCFRunLoopSourceAsync (factory, cancellationToken).ConfigureAwait (false);
}
}
[DllImport (Constants.CFNetworkLibrary)]
extern static /* CFRunLoopSourceRef __nonnull */ IntPtr CFNetworkExecuteProxyAutoConfigurationURL (
/* CFURLRef __nonnull */ IntPtr proxyAutoConfigurationURL,
/* CFURLRef __nonnull */ IntPtr targetURL,
/* CFProxyAutoConfigurationResultCallback __nonnull */ IntPtr cb,
/* CFStreamClientContext * __nonnull */ IntPtr clientContext);
/* CFProxyAutoConfigurationResultCallback __nonnull */ CFProxyAutoConfigurationResultCallbackInternal cb,
/* CFStreamClientContext * __nonnull */ ref CFStreamClientContext clientContext);
public static CFRunLoopSource ExecuteProxyAutoConfigurationURL (NSUrl proxyAutoConfigurationURL, NSUrl targetURL, CFProxyAutoConfigurationResultCallback resultCallback, CFStreamClientContext clientContext)
{
if (proxyAutoConfigurationURL == null)
throw new ArgumentNullException ("proxyAutoConfigurationURL");
public static CFProxy[] ExecuteProxyAutoConfigurationUrl (Uri proxyAutoConfigurationUrl, Uri targetUrl, out NSError outError)
{
outError = null;
if (proxyAutoConfigurationUrl == null)
throw new ArgumentNullException (nameof (proxyAutoConfigurationUrl));
if (targetURL == null)
throw new ArgumentNullException ("targetURL");
if (targetUrl == null)
throw new ArgumentNullException (nameof (targetUrl));
if (resultCallback == null)
throw new ArgumentNullException ("resultCallback");
if (clientContext == null)
throw new ArgumentNullException ("clientContext");
IntPtr source = CFNetworkExecuteProxyAutoConfigurationURL (proxyAutoConfigurationURL.Handle, targetURL.Handle, resultCallback, clientContext);
return (source == IntPtr.Zero) ? null : new CFRunLoopSource (source);
using (var pacUrl = new NSUrl (proxyAutoConfigurationUrl.AbsoluteUri)) // toll free bridge to CFUrl
using (var url = new NSUrl (targetUrl.AbsoluteUri)) {
CreatePACCFRunLoopSource factory = delegate (CFProxyAutoConfigurationResultCallbackInternal cb, ref CFStreamClientContext context) {
return CFNetworkExecuteProxyAutoConfigurationURL (pacUrl.Handle, url.Handle, cb, ref context);
};
return ExecutePacCFRunLoopSourceBlocking (factory, out outError);
}
}
public static async Task<(CFProxy[] proxies, NSError error)> ExecuteProxyAutoConfigurationUrlAsync (Uri proxyAutoConfigurationUrl, Uri targetUrl, CancellationToken cancellationToken)
{
// similar to the sync method, but we will spawn a thread and wait in an async manner to an autoreset event to be fired
if (proxyAutoConfigurationUrl == null)
throw new ArgumentNullException (nameof (proxyAutoConfigurationUrl));
if (targetUrl == null)
throw new ArgumentNullException (nameof (targetUrl));
using (var pacUrl = new NSUrl (proxyAutoConfigurationUrl.AbsoluteUri)) // toll free bridge to CFUrl
using (var url = new NSUrl (targetUrl.AbsoluteUri)) {
CreatePACCFRunLoopSource factory = delegate (CFProxyAutoConfigurationResultCallbackInternal cb, ref CFStreamClientContext context) {
return CFNetworkExecuteProxyAutoConfigurationURL (pacUrl.Handle, url.Handle, cb, ref context);
};
// use the helper task with a factory for this method
return await ExecutePacCFRunLoopSourceAsync (factory, cancellationToken).ConfigureAwait (false);
}
}
#endif
class CFWebProxy : IWebProxy {
ICredentials credentials;

Просмотреть файл

@ -8,6 +8,8 @@
//
using System;
using System.Threading;
using System.IO;
#if XAMCORE_2_0
using Foundation;
using CoreFoundation;
@ -42,5 +44,215 @@ namespace MonoTouchFixtures.CoreFoundation {
Dlfcn.dlclose (lib);
}
}
#if !__WATCHOS__ && !MONOMAC
[Test]
public void TestPACParsingScript ()
{
// get the path for the pac file, try to parse it and ensure that
// our cb was called
string pacPath = Path.Combine (NSBundle.MainBundle.BundlePath, "example.pac");
NSError error = null;
var script = File.ReadAllText (pacPath);
var targetUri = new Uri ("http://docs.xamarin.com");
var proxies = CFNetwork.ExecuteProxyAutoConfigurationScript (script, targetUri, out error);
Assert.IsNull (error, "Null error");
Assert.AreEqual (1, proxies.Length, "Length");
// assert the data of the proxy, although we are really testing the js used
Assert.AreEqual (8080, proxies [0].Port, "Port");
}
[Test]
public void TestPACParsingScriptNoProxy ()
{
string pacPath = Path.Combine (NSBundle.MainBundle.BundlePath, "example.pac");
NSError error = null;
var script = File.ReadAllText (pacPath);
var targetUri = new Uri ("http://google.com");
var proxies = CFNetwork.ExecuteProxyAutoConfigurationScript (script, targetUri, out error);
Assert.IsNull (error, "Null error");
Assert.IsNotNull (proxies, "Not null proxies");
Assert.AreEqual (1, proxies.Length, "Proxies length");
Assert.AreEqual (CFProxyType.None, proxies [0].ProxyType);
}
[Test]
public void TestPACParsingScriptError ()
{
NSError error = null;
var script = "Not VALID js";
var targetUri = new Uri ("http://google.com");
var proxies = CFNetwork.ExecuteProxyAutoConfigurationScript (script, targetUri, out error);
Assert.IsNotNull (error, "Not null error");
Assert.IsNull (proxies, "Null proxies");
}
[Test]
public void TestPACParsingAsync ()
{
CFProxy [] proxies = null;
NSError error = null;
NSObject cbClient = null;
bool done = false;
string pacPath = Path.Combine (NSBundle.MainBundle.BundlePath, "example.pac");
var script = File.ReadAllText (pacPath);
var targetUri = new Uri ("http://docs.xamarin.com");
Exception ex;
bool foundProxies;
// similar to the other tests, but we want to ensure that the async/await API works
TestRuntime.RunAsync (DateTime.Now.AddSeconds (30), async () => {
try {
CancellationTokenSource cancelSource = new CancellationTokenSource ();
CancellationToken cancelToken = cancelSource.Token;
var result = await CFNetwork.ExecuteProxyAutoConfigurationScriptAsync (script, targetUri, cancelToken);
proxies = result.proxies;
error = result.error;
} catch (Exception e) {
ex = e;
} finally {
done = true;
}
}, () => done);
Assert.IsNull (cbClient, "Null client");
Assert.IsNull (error, "Null error");
Assert.IsNotNull (proxies, "Not null proxies");
Assert.AreEqual (1, proxies.Length, "Length");
// assert the data of the proxy, although we are really testing the js used
Assert.AreEqual (8080, proxies [0].Port, "Port");
}
[Test]
public void TestPACParsingAsyncNoProxy ()
{
CFProxy [] proxies = null;
NSError error = null;
NSObject cbClient = null;
bool done = false;
string pacPath = Path.Combine (NSBundle.MainBundle.BundlePath, "example.pac");
var script = File.ReadAllText (pacPath);
var targetUri = new Uri ("http://docs.xamarin.com");
Exception ex;
bool foundProxies;
// similar to the other tests, but we want to ensure that the async/await API works
TestRuntime.RunAsync (DateTime.Now.AddSeconds (30), async () => {
try {
CancellationTokenSource cancelSource = new CancellationTokenSource ();
CancellationToken cancelToken = cancelSource.Token;
var result = await CFNetwork.ExecuteProxyAutoConfigurationScriptAsync (script, targetUri, cancelToken);
proxies = result.proxies;
error = result.error;
} catch (Exception e) {
ex = e;
} finally {
done = true;
}
}, () => done);
Assert.IsNull (cbClient, "Null client");
Assert.IsNull (error, "Null error");
Assert.IsNotNull (proxies, "Not null proxies");
Assert.AreEqual (1, proxies.Length, "Proxies length");
Assert.AreEqual (CFProxyType.None, proxies [0].ProxyType);
}
[Test]
public void TestPACParsingUrl ()
{
NSError error;
string pacPath = Path.Combine (NSBundle.MainBundle.BundlePath, "example.pac");
var pacUri = new Uri (pacPath);
var targetUri = new Uri ("http://docs.xamarin.com");
var proxies = CFNetwork.ExecuteProxyAutoConfigurationUrl (pacUri, targetUri, out error);
Assert.IsNull (error, "Null error");
Assert.AreEqual (1, proxies.Length, "Length");
// assert the data of the proxy, although we are really testing the js used
Assert.AreEqual (8080, proxies [0].Port, "Port");
}
[Test]
public void TestPacParsingUrlNoProxy ()
{
NSError error;
string pacPath = Path.Combine (NSBundle.MainBundle.BundlePath, "example.pac");
var pacUri = new Uri (pacPath);
var targetUri = new Uri ("http://google.com");
var proxies = CFNetwork.ExecuteProxyAutoConfigurationUrl (pacUri, targetUri, out error);
Assert.IsNull (error, "Null error");
Assert.IsNotNull (proxies, "Not null proxies");
Assert.AreEqual (1, proxies.Length, "Proxies length");
Assert.AreEqual (CFProxyType.None, proxies [0].ProxyType);
}
[Test]
public void TestPACParsingUrlAsync ()
{
CFProxy [] proxies = null;
NSError error = null;
NSObject cbClient = null;
bool done = false;
string pacPath = Path.Combine (NSBundle.MainBundle.BundlePath, "example.pac");
var pacUri = new Uri (pacPath);
var targetUri = new Uri ("http://docs.xamarin.com");
Exception ex;
bool foundProxies;
// similar to the other tests, but we want to ensure that the async/await API works
TestRuntime.RunAsync (DateTime.Now.AddSeconds (30), async () => {
try {
CancellationTokenSource cancelSource = new CancellationTokenSource ();
CancellationToken cancelToken = cancelSource.Token;
var result = await CFNetwork.ExecuteProxyAutoConfigurationUrlAsync (pacUri, targetUri, cancelToken);
proxies = result.proxies;
error = result.error;
} catch (Exception e) {
ex = e;
} finally {
done = true;
}
}, () => done);
Assert.IsNull (cbClient, "Null client");
Assert.IsNull (error, "Null error");
Assert.IsNotNull (proxies, "Not null proxies");
Assert.AreEqual (1, proxies.Length, "Length");
// assert the data of the proxy, although we are really testing the js used
Assert.AreEqual (8080, proxies [0].Port, "Port");
}
[Test]
public void TestPACParsingUrlAsyncNoProxy ()
{
CFProxy [] proxies = null;
NSError error = null;
NSObject cbClient = null;
bool done = false;
string pacPath = Path.Combine (NSBundle.MainBundle.BundlePath, "example.pac");
var pacUri = new Uri (pacPath);
var targetUri = new Uri ("http://docs.google.com");
Exception ex;
bool foundProxies;
// similar to the other tests, but we want to ensure that the async/await API works
TestRuntime.RunAsync (DateTime.Now.AddSeconds (30), async () => {
try {
CancellationTokenSource cancelSource = new CancellationTokenSource ();
CancellationToken cancelToken = cancelSource.Token;
var result = await CFNetwork.ExecuteProxyAutoConfigurationUrlAsync (pacUri, targetUri, cancelToken);
proxies = result.proxies;
error = result.error;
} catch (Exception e) {
ex = e;
} finally {
done = true;
}
}, () => done);
Assert.IsNull (cbClient, "Null client");
Assert.IsNull (error, "Null error");
Assert.IsNotNull (proxies, "Not null proxies");
Assert.AreEqual (1, proxies.Length, "Proxies length");
Assert.AreEqual (CFProxyType.None, proxies [0].ProxyType);
}
#endif
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Exmaple PAC file that returns a proxy for the urls that have the
// xamarin domain name.
function FindProxyForURL(url, host) {
if (dnsDomainIs(host, "xamarin.com"))
return "PROXY example.com:8080";
if (dnsDomainIs(host, "google.com"))
return "DIRECT";
}

Просмотреть файл

@ -321,6 +321,7 @@
<BundleResource Include="compressed_lz4" />
<BundleResource Include="compressed_lzma" />
<BundleResource Include="compressed_zip" />
<BundleResource Include="example.pac" />
</ItemGroup>
<ItemGroup>
<Metal Include="Resources\metal-sample.metal" Condition="'$(Platform)' != 'iPhoneSimulator' " />

Просмотреть файл

@ -150,8 +150,6 @@
!missing-pinvoke! CFNetServiceSetClient is not bound
!missing-pinvoke! CFNetServiceSetTXTData is not bound
!missing-pinvoke! CFNetServiceUnscheduleFromRunLoop is not bound
!missing-pinvoke! CFNetworkExecuteProxyAutoConfigurationScript is not bound
!missing-pinvoke! CFNetworkExecuteProxyAutoConfigurationURL is not bound
!missing-pinvoke! CFReadStreamCreateWithFTPURL is not bound
!missing-pinvoke! CFStreamCreatePairWithSocketToNetService is not bound
!missing-pinvoke! CFWriteStreamCreateWithFTPURL is not bound