Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
CFProxySupportTool.c
/* |
File: CFProxySupportTool.c |
Contains: Shows the use of the APIs in "CFProxySupport.h". |
Written by: DTS |
Copyright: Copyright (c) 2007 Apple Inc. All Rights Reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. |
("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of |
these terms. If you do not agree with these terms, please do |
not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following |
terms, and subject to these terms, Apple grants you a personal, |
non-exclusive license, under Apple's copyrights in this |
original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or |
without modifications, in source and/or binary forms; provided |
that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the |
following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks |
or logos of Apple Inc. may be used to endorse or promote |
products derived from the Apple Software without specific prior |
written permission from Apple. Except as expressly stated in |
this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or |
by other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. |
APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING |
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, |
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING |
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, |
INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY |
OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY |
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR |
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF |
SUCH DAMAGE. |
Change History (most recent first): |
$Log$ |
*/ |
#include <arpa/inet.h> |
#include <assert.h> |
#include <errno.h> |
#include <netdb.h> |
#include <netinet/in.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <sys/socket.h> |
#include <unistd.h> |
#include <CoreServices/CoreServices.h> |
#include <SystemConfiguration/SystemConfiguration.h> |
#pragma mark ***** Infrastructure |
// In some cases we need to use asynchronous APIs as if they were synchronous. |
// So, we add the asynchronous API's runloop source to the runloop, and then |
// sit inside CFRunLoopRunInMode waiting for things to complete. We use a custom |
// mode as an example of how you would do this in a real application (of course |
// blocking for a long time in a real application would be a mistake). It's |
// easy to make the mistake of blocking in kCFRunLoopDefaultMode. That's a |
// problem in a real application, where all sorts of other things (like |
// event loop timers) are attached to the default mode, and they end up running |
// unexpectedly. |
#define kPrivateRunLoopMode CFSTR("com.apple.dts.CFProxySupportTool") |
static void CFQRelease(CFTypeRef cf) |
// Trivial wrapper for CFRelease that treats NULL as a no-op. |
{ |
if (cf != NULL) { |
CFRelease(cf); |
} |
} |
static const char *EasyToUseButNotThreadSafeCStringForCFString(CFStringRef str) |
// Returns a UTF-8 C string representation of the input CFString. This isn't |
// thread safe, but it is very easy to use. |
// |
// WARNING: Don't do something like this: |
// |
// printf( |
// "%s = %s\n", |
// EasyToUseButNotThreadSafeCStringForCFString(key), |
// EasyToUseButNotThreadSafeCStringForCFString(value) |
// ); |
// |
// The second call to EasyToUseButNotThreadSafeCStringForCFString will invalidate |
// the results of the first. |
{ |
Boolean success; |
static char * sCStr; |
size_t cStrSize; |
assert(str != NULL); |
free(sCStr); |
cStrSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); |
sCStr = malloc(cStrSize); |
assert(sCStr != NULL); |
success = CFStringGetCString(str, sCStr, cStrSize, kCFStringEncodingUTF8); |
assert(success); |
return sCStr; |
} |
static void PrintAndRemoveIfPresentProxyDetail( |
CFMutableDictionaryRef proxyMutable, |
CFStringRef key, |
CFTypeID expectedTypeID, |
const char * name, |
Boolean printAsBullets |
) |
// Check to see if the key is present in proxyMutable. If it is, print its |
// value (using name is the user-visible field name) and remove it from the |
// proxy dictionary. |
// |
// Also, verify that the type of the value matches expectedTypeID. |
// |
// Finally, if printAsBullets is true, print the value as bullets rather |
// than the actual value (which is likely to be a password). |
{ |
assert(proxyMutable != NULL); |
assert(key != NULL); |
assert(name != NULL); |
if (CFDictionaryContainsKey(proxyMutable, key)) { |
CFTypeRef value; |
value = CFDictionaryGetValue(proxyMutable, key); |
assert(value != NULL); |
assert(CFGetTypeID(value) == expectedTypeID); |
if (printAsBullets) { |
assert(expectedTypeID == CFStringGetTypeID()); |
fprintf(stdout, " %s = ********\n", name); |
} else { |
CFStringRef desc; |
if ( (CFGetTypeID(value) == CFStringGetTypeID()) |
|| (CFGetTypeID(value) == CFNumberGetTypeID()) |
|| (CFGetTypeID(value) == CFURLGetTypeID()) ) { |
desc = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), value); |
} else { |
desc = CFCopyDescription(value); |
} |
assert(desc != NULL); |
fprintf(stdout, " %s = %s\n", name, EasyToUseButNotThreadSafeCStringForCFString(desc)); |
CFQRelease(desc); |
} |
// Do this last, otherwise the retain count on value might drop to zero. |
CFDictionaryRemoveValue(proxyMutable, key); |
} |
} |
static void PrintAndRemoveIfPresentProxyDetails(CFMutableDictionaryRef proxyMutable) |
// Print all of the well known properties in a proxy dictionary, |
// removing them from the dictionary if they're present. |
{ |
PrintAndRemoveIfPresentProxyDetail(proxyMutable, kCFProxyHostNameKey, CFStringGetTypeID(), "host", false); |
PrintAndRemoveIfPresentProxyDetail(proxyMutable, kCFProxyPortNumberKey, CFNumberGetTypeID(), "port", false); |
PrintAndRemoveIfPresentProxyDetail(proxyMutable, kCFProxyUsernameKey, CFStringGetTypeID(), "username", false); |
PrintAndRemoveIfPresentProxyDetail(proxyMutable, kCFProxyPasswordKey, CFStringGetTypeID(), "password", true); |
PrintAndRemoveIfPresentProxyDetail(proxyMutable, kCFProxyAutoConfigurationURLKey, CFURLGetTypeID(), "url", false); |
} |
static void PrintProxy(CFIndex proxyIndex, CFDictionaryRef proxy) |
// Print a single proxy dictionary. |
{ |
CFMutableDictionaryRef proxyMutable; |
CFStringRef proxyType; |
const char * proxyTypeCStr; |
Boolean printUnexpectedMessage; |
printUnexpectedMessage = true; |
// Create a mutable copy. We work with this copy. As we process keys we remove |
// them from the mutable copy. When we get to the end we check whether we removed |
// all of the keys. If not, that's weird and we tell the user. |
proxyMutable = CFDictionaryCreateMutableCopy(NULL, CFDictionaryGetCount(proxy), proxy); |
assert(proxyMutable != NULL); |
// Get the type of the proxy and dispatch off that. |
proxyType = (CFStringRef) CFDictionaryGetValue(proxyMutable, kCFProxyTypeKey); |
assert(proxyType != NULL); |
assert(CFGetTypeID(proxyType) == CFStringGetTypeID()); |
if (CFEqual(proxyType, kCFProxyTypeNone)) { |
proxyTypeCStr = "no proxy"; |
} else if (CFEqual(proxyType, kCFProxyTypeHTTP)) { |
proxyTypeCStr = "HTTP proxy"; |
} else if (CFEqual(proxyType, kCFProxyTypeHTTPS)) { |
proxyTypeCStr = "HTTPS proxy"; |
} else if (CFEqual(proxyType, kCFProxyTypeSOCKS)) { |
proxyTypeCStr = "SOCKS proxy"; |
} else if (CFEqual(proxyType, kCFProxyTypeFTP)) { |
proxyTypeCStr = "FTP proxy"; |
} else if (CFEqual(proxyType, kCFProxyTypeAutoConfigurationURL)) { |
proxyTypeCStr = "PAC proxy"; |
} else { |
proxyTypeCStr = "unknown proxy"; |
printUnexpectedMessage = true; |
} |
fprintf( |
stdout, |
"%d %s (%s)\n", |
(int) proxyIndex, |
proxyTypeCStr, |
EasyToUseButNotThreadSafeCStringForCFString(proxyType) |
); |
PrintAndRemoveIfPresentProxyDetails(proxyMutable); |
// Do this last, otherwise the retain count on proxyType might drop to zero. |
CFDictionaryRemoveValue(proxyMutable, kCFProxyTypeKey); |
// Check to see if any keys got missed. |
if (CFDictionaryGetCount(proxyMutable) != 0) { |
if ( printUnexpectedMessage ) { |
fprintf(stderr, "proxy dictionary contains unexpected keys\n"); |
} |
CFShow(proxyMutable); |
} |
CFQRelease(proxyMutable); |
} |
static void PrintProxies(CFArrayRef proxies) |
// Print a proxies array. |
{ |
CFIndex proxyCount; |
CFIndex proxyIndex; |
assert(proxies != NULL); |
// CFShow(proxies); |
proxyCount = CFArrayGetCount(proxies); |
for (proxyIndex = 0; proxyIndex < proxyCount; proxyIndex++) { |
CFDictionaryRef thisProxy; |
thisProxy = (CFDictionaryRef) CFArrayGetValueAtIndex(proxies, proxyIndex); |
assert(CFGetTypeID(thisProxy) == CFDictionaryGetTypeID()); |
PrintProxy(proxyIndex, thisProxy); |
} |
} |
#pragma mark ***** Commands |
static OSStatus ProxiesForURL(const char *urlStr) |
// Implements the "proxiesForURL" command. |
{ |
OSStatus err; |
CFURLRef url; |
CFDictionaryRef proxySettings; |
CFArrayRef proxies; |
url = NULL; |
proxySettings = NULL; |
proxies = NULL; |
// Create a URL from the argument C string. |
err = noErr; |
url = CFURLCreateWithBytes(NULL, (const UInt8 *) urlStr, strlen(urlStr), kCFStringEncodingUTF8, NULL); |
if (url == NULL) { |
err = coreFoundationUnknownErr; |
} |
// Get the default proxies dictionary from CF. |
if (err == noErr) { |
proxySettings = SCDynamicStoreCopyProxies(NULL); |
if (proxySettings == NULL) { |
err = coreFoundationUnknownErr; |
} |
} |
// Call CFNetworkCopyProxiesForURL and print the results. |
if (err == noErr) { |
proxies = CFNetworkCopyProxiesForURL(url, proxySettings); |
if (proxies == NULL) { |
err = coreFoundationUnknownErr; |
} |
} |
if (err == noErr) { |
PrintProxies(proxies); |
} |
// Clean up. |
CFQRelease(proxies); |
CFQRelease(proxySettings); |
CFQRelease(url); |
return err; |
} |
static OSStatus ProxiesForURLUsingScript(const char *urlStr, const char *scriptPath) |
// Implements the "proxiesForURLUsingScript" command. |
{ |
OSStatus err; |
Boolean success; |
CFURLRef url; |
CFURLRef scriptURL; |
CFDataRef scriptData; |
CFStringRef scriptStr; |
CFArrayRef proxies; |
SInt32 readErr; |
url = NULL; |
scriptURL = NULL; |
scriptData = NULL; |
scriptStr = NULL; |
proxies = NULL; |
// Create CFURLs from the input parameters. |
err = noErr; |
url = CFURLCreateWithBytes(NULL, (const UInt8 *) urlStr, strlen(urlStr), kCFStringEncodingUTF8, NULL); |
if (url == NULL) { |
err = coreFoundationUnknownErr; |
} |
if (err == noErr) { |
scriptURL = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *) scriptPath, strlen(scriptPath), false); |
if (scriptURL == NULL) { |
err = coreFoundationUnknownErr; |
} |
} |
// Read the contents of the script file. |
if (err == noErr) { |
success = CFURLCreateDataAndPropertiesFromResource(NULL, scriptURL, &scriptData, NULL, NULL, &readErr); |
if ( ! success ) { |
err = readErr; |
assert(err != noErr); |
} |
} |
if (err == noErr) { |
scriptStr = CFStringCreateWithBytes(NULL, CFDataGetBytePtr(scriptData), CFDataGetLength(scriptData), kCFStringEncodingUTF8, true); |
if (scriptStr == NULL) { |
err = coreFoundationUnknownErr; |
} |
} |
// Run the script and print the results. |
if (err == noErr) { |
// Work around <rdar://problem/5530166>. This dummy call to |
// CFNetworkCopyProxiesForURL initialise some state within CFNetwork |
// that is required by CFNetworkCopyProxiesForAutoConfigurationScript. |
(void) CFNetworkCopyProxiesForURL(url, NULL); |
proxies = CFNetworkCopyProxiesForAutoConfigurationScript(scriptStr, url); |
if (proxies == NULL) { |
err = coreFoundationUnknownErr; |
} |
} |
if (err == noErr) { |
PrintProxies(proxies); |
} |
// Clean up. |
CFQRelease(url); |
CFQRelease(scriptURL); |
CFQRelease(scriptData); |
CFQRelease(scriptStr); |
CFQRelease(proxies); |
return err; |
} |
static void ResultCallback(void * client, CFArrayRef proxies, CFErrorRef error) |
// Callback for CFNetworkExecuteProxyAutoConfigurationURL. client is a |
// pointer to a CFTypeRef. This stashes either error or proxies in that |
// location. |
{ |
CFTypeRef * resultPtr; |
assert( (proxies != NULL) == (error == NULL) ); |
resultPtr = (CFTypeRef *) client; |
assert( resultPtr != NULL); |
assert(*resultPtr == NULL); |
if (error != NULL) { |
*resultPtr = CFRetain(error); |
} else { |
*resultPtr = CFRetain(proxies); |
} |
CFRunLoopStop(CFRunLoopGetCurrent()); |
} |
static OSStatus ProxiesForURLUsingScriptURL(const char *urlStr, const char *scriptURLStr) |
// Implements the "proxiesForURLUsingScriptURL" command. |
{ |
OSStatus err; |
CFURLRef url; |
CFURLRef scriptURL; |
CFArrayRef proxies; |
CFRunLoopSourceRef rls; |
CFTypeRef result; |
url = NULL; |
scriptURL = NULL; |
proxies = NULL; |
rls = NULL; |
result = NULL; |
// Create CFURLs from the input parameters. |
err = noErr; |
url = CFURLCreateWithBytes(NULL, (const UInt8 *) urlStr, strlen(urlStr), kCFStringEncodingUTF8, NULL); |
if (url == NULL) { |
err = coreFoundationUnknownErr; |
} |
if (err == noErr) { |
scriptURL = CFURLCreateWithBytes(NULL, (const UInt8 *) scriptURLStr, strlen(scriptURLStr), kCFStringEncodingUTF8, NULL); |
if (scriptURL == NULL) { |
err = coreFoundationUnknownErr; |
} |
} |
// Run the script and print the results. |
// |
// Note: CFNetworkExecuteProxyAutoConfigurationURL is an async |
// routine, but we just treat it as a synchronous routine by attaching |
// the returned runloop source to our runloop and then running the runloop. |
// A real application would return to runloop and pick things up in the |
// CFNetworkExecuteProxyAutoConfigurationURL results callback. |
if (err == noErr) { |
CFStreamClientContext context = { 0, &result, NULL, NULL, NULL }; |
// Work around <rdar://problem/5530166>. This dummy call to |
// CFNetworkCopyProxiesForURL initialise some state within CFNetwork |
// that is required by CFNetworkCopyProxiesForAutoConfigurationScript. |
(void) CFNetworkCopyProxiesForURL(url, NULL); |
rls = CFNetworkExecuteProxyAutoConfigurationURL(scriptURL, url, ResultCallback, &context); |
if (rls == NULL) { |
err = coreFoundationUnknownErr; |
} |
// Despite the fact that CFNetworkExecuteProxyAutoConfigurationURL has |
// neither a "Create" nor a "Copy" in the name, we are required to |
// release the return CFRunLoopSourceRef <rdar://problem/5533931>. |
} |
if (err == noErr) { |
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kPrivateRunLoopMode); |
CFRunLoopRunInMode(kPrivateRunLoopMode, 1.0e10, false); |
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), rls, kPrivateRunLoopMode); |
// Once the runloop returns, we should have either an error result or a |
// proxies array result. Do the appropriate thing with that result. |
assert(result != NULL); |
if ( CFGetTypeID(result) == CFErrorGetTypeID() ) { |
if ( CFEqual(CFErrorGetDomain( (CFErrorRef) result ), kCFErrorDomainOSStatus) ) { |
err = CFErrorGetCode( (CFErrorRef) result ); |
} else { |
err = coreFoundationUnknownErr; |
} |
} else if ( CFGetTypeID(result) == CFArrayGetTypeID() ) { |
PrintProxies(result); |
} else { |
assert(false); |
err = kernelTimeoutErr; |
} |
} |
// Clean up. |
CFQRelease(result); |
CFQRelease(rls); |
CFQRelease(proxies); |
CFQRelease(scriptURL); |
CFQRelease(url); |
return err; |
} |
// IMPORTANT |
// The next few routines (specifically, HTTPGetAndPrint, |
// InitSockAddrFromHostCFStringAndPort, HTTPGetAndPrintFromURL, and |
// HTTPGetAndPrintFromProxy) represent a bone-headed implementation of HTTP using |
// BSD APIs. This code is present purely to show how you can use CFProxySupport |
// to glue non-CFNetwork based networking code to the system-specified |
// proxies. It is not meant to be a good example of BSD programming. |
// |
// For example, this code: |
// |
// o does not support IPv6 |
// |
// o uses an ancient interface to the DNS (<x-man-page://3/gethostbyname> |
// when it should be using <x-man-page://3/getaddrinfo>) |
// |
// o does not connect to all of the addresses returned by the DNS; it |
// just tries the first one then gives up |
// |
// o does not handle short writes (see comments in HTTPGetAndPrint) |
// |
// In summary, don't copy and paste this part of the sapmle code into a real |
// product. |
static OSStatus HTTPGetAndPrint(const char *resource, const struct sockaddr_in *addr) |
// This is a bone-headed implementation of HTTP using straight BSD sockets. |
// It connects to the server specified by addr, requests the specified |
// resource, and prints the result to stdout. |
{ |
int err; |
char * httpRequest; |
int fd; |
int junk; |
ssize_t bytesWritten; |
ssize_t bytesRead; |
ssize_t bufIndex; |
char buffer[1024]; |
httpRequest = NULL; |
// Create the HTTP request string. Note that we specify HTTP/1.0 because |
// we /so/ aren't prepared to handle HTTP/1.0 (-: |
(void) asprintf(&httpRequest, "GET %s HTTP/1.0\r\n\r\n", resource); |
// Create and connect the socket. |
err = 0; |
fd = socket(AF_INET, SOCK_STREAM, 0); |
if (fd < 0) { |
err = errno; |
} |
if (err == 0) { |
err = connect(fd, (const struct sockaddr *) addr, addr->sin_len); |
if (err < 0) { |
err = errno; |
} |
} |
// Send the request. Note that we don't handle short writes (that is, |
// write returning a positive number less than strlen(httpRequest). |
// In practice this never happens for us (because strlen(httpRequest) is |
// much less than the size of the socket buffer), but a robust |
// implementation must handle this. |
// |
// Similarly, we don't handle EINTR because this write should never block. |
if (err == 0) { |
bytesWritten = write(fd, httpRequest, strlen(httpRequest)); |
if (bytesWritten < 0) { |
err = errno; |
} else { |
assert(bytesWritten == (ssize_t) strlen(httpRequest)); |
} |
} |
// Read and print the response. We completely ignore text encodings here. |
// If the server responds with ISO Latin1, as many do, and we print it to |
// Terminal (which is expecting UTF-8), we're going to print gibberish. |
if (err == 0) { |
do { |
bytesRead = read(fd, buffer, sizeof(buffer)); |
if (bytesRead < 0) { |
err = errno; |
if (err == EINTR) { |
err = 0; |
} |
} else if (bytesRead > 0) { |
for (bufIndex = 0; bufIndex < bytesRead; bufIndex++) { |
if (buffer[bufIndex] != '\r') { |
(void) fputc(buffer[bufIndex], stdout); |
} |
} |
} |
} while ( (err == 0) && (bytesRead != 0) ); |
} |
// Clean up. |
if (fd >= 0) { |
junk = close(fd); |
assert(junk == 0); |
} |
free(httpRequest); |
// Convert errno-style error to OSStatus-style error per Q&A 1499. |
// |
// <http://developer.apple.com/qa/qa2006/qa1499.html> |
return (err == 0) ? noErr : ((OSStatus) (100000 + err)); |
} |
static OSStatus InitSockAddrFromHostCFStringAndPort( |
CFStringRef host, |
uint16_t port, |
struct sockaddr_in * addrPtr |
) |
// Set up a (struct sockaddr_in) by resolving the specified host to an |
// IP address. |
{ |
OSStatus err; |
Boolean success; |
char hostStr[NI_MAXHOST]; |
const struct hostent * hostEnt; |
// Get the host string as ASCII (not UTF-8 -- the DNS uses ASCII!). |
err = noErr; |
success = CFStringGetCString(host, hostStr, sizeof(hostStr), kCFStringEncodingASCII); |
if ( ! success ) { |
err = coreFoundationUnknownErr; |
} |
// Resolve it. This should be using <x-man-page://3/getaddrinfo>, but |
// gethostbyname is simpler and we're not going to handle IPv6 anyway. |
if (err == noErr) { |
hostEnt = gethostbyname(hostStr);; |
if (hostEnt == NULL) { |
// Convert errno-style error to OSStatus-style error per Q&A 1499. |
// |
// <http://developer.apple.com/qa/qa2006/qa1499.html> |
err = (OSStatus) (100000 + ENOENT); |
} |
} |
// Fill out the (struct sockaddr_in). Note that this uses just the first |
// IP address for the host. A real implementation would have to try |
// each address in turn. |
if (err == noErr) { |
memset(addrPtr, 0, sizeof(*addrPtr)); |
addrPtr->sin_len = sizeof(*addrPtr); |
addrPtr->sin_family = AF_INET; |
addrPtr->sin_port = htons(port); |
addrPtr->sin_addr.s_addr = *((in_addr_t *)hostEnt->h_addr); |
} |
return err; |
} |
static OSStatus HTTPGetAndPrintFromURL(CFURLRef url) |
// Use direct HTTP to get and print the resource at the specifier URL. |
{ |
OSStatus err; |
CFStringRef host; |
SInt32 port; |
struct sockaddr_in hostAddr; |
CFRange pathRange; |
host = NULL; |
// Create a (struct sockaddr_in) from the URL. |
port = CFURLGetPortNumber(url); |
if (port == -1) { |
port = 80; // If there's no port, default to port 80. |
} |
err = noErr; |
host = CFURLCopyHostName(url); |
if (host == NULL) { |
err = coreFoundationUnknownErr; |
} |
if (err == noErr) { |
err = InitSockAddrFromHostCFStringAndPort(host, (uint16_t) port, &hostAddr); |
} |
// Do the HTTP request. Note that this code is complicated, somewhat, |
// by the fact that we want to send the path component (and everything after |
// it) to the server. So, we get the URL as bytes and then find the path |
// component and use its /start/ as the 'the rest of the URL'. Also, |
// if path component is empty, we just send a "/". |
if (err == noErr) { |
CFIndex bufSize; |
char * buf; |
const char * path; |
bufSize = CFURLGetBytes(url, NULL, 0) + 1; |
buf = malloc(bufSize); |
assert(buf != NULL); |
(void) CFURLGetBytes(url, (UInt8 *) buf, bufSize - 1); |
buf[bufSize - 1] = 0; |
pathRange = CFURLGetByteRangeForComponent(url, kCFURLComponentPath, NULL); |
if (pathRange.length == 0) { |
path = "/"; |
} else { |
path = &buf[pathRange.location]; |
} |
err = HTTPGetAndPrint(path, &hostAddr); |
free(buf); |
} |
CFQRelease(host); |
return noErr; |
} |
static OSStatus HTTPGetAndPrintFromProxy(CFURLRef url, CFDictionaryRef thisProxy) |
// Use an HTTP proxy to get and print the resource at the specifier URL. |
{ |
OSStatus err; |
Boolean success; |
CFStringRef proxyHost; |
CFNumberRef proxyPortNum; |
SInt32 proxyPort; |
struct sockaddr_in proxyAddr; |
// Create a (struct sockaddr_in) from the proxy host and port. |
err = noErr; |
proxyHost = CFDictionaryGetValue(thisProxy, kCFProxyHostNameKey); |
if (proxyHost == NULL) { |
err = coreFoundationUnknownErr; |
} else { |
assert(CFGetTypeID(proxyHost) == CFStringGetTypeID()); |
} |
if (err == noErr) { |
proxyPortNum = CFDictionaryGetValue(thisProxy, kCFProxyPortNumberKey); |
if (proxyPortNum == NULL) { |
proxyPort = 8080; // If there's no port, default to port 8080. |
} else { |
assert(CFGetTypeID(proxyPortNum) == CFNumberGetTypeID()); |
success = CFNumberGetValue(proxyPortNum, kCFNumberSInt32Type, &proxyPort); |
if ( ! success ) { |
err = coreFoundationUnknownErr; |
} |
} |
} |
if (err == noErr) { |
err = InitSockAddrFromHostCFStringAndPort(proxyHost, (uint16_t) proxyPort, &proxyAddr); |
} |
// Do the HTTP request to the proxy. As the proxy expects the entire URL, |
// this is much easier than the HTTPGetAndPrintFromURL case. |
if (err == noErr) { |
CFIndex bufSize; |
char * buf; |
bufSize = CFURLGetBytes(url, NULL, 0) + 1; |
buf = malloc(bufSize); |
assert(buf != NULL); |
(void) CFURLGetBytes(url, (UInt8 *) buf, bufSize - 1); |
buf[bufSize - 1] = 0; |
err = HTTPGetAndPrint(buf, &proxyAddr); |
free(buf); |
} |
return err; |
} |
static OSStatus CreateProxyListWithExpandedPACProxies( |
CFURLRef url, |
CFArrayRef proxies, |
CFArrayRef * expandedProxiesPtr |
) |
// proxies is the array of proxies for the URL specified by url. Create |
// a new array of proxies, where any PAC-based proxy is replaced by |
// the results of running the PAC script. |
{ |
OSStatus err; |
CFMutableArrayRef expandedProxies; |
CFIndex proxyCount; |
CFIndex proxyIndex; |
assert(proxies != NULL); |
assert( expandedProxiesPtr != NULL); |
assert(*expandedProxiesPtr == NULL); |
expandedProxies = NULL; |
// Start with an empty results array. |
err = noErr; |
expandedProxies = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); |
if (expandedProxies == NULL) { |
err = coreFoundationUnknownErr; |
} |
// For each incoming proxy, if it's not a PAC-based proxy, just add the proxy |
// to the results array. If it /is/ a PAC-based proxy, run the PAC script |
// and append its results to the array. |
if (err == noErr) { |
proxyCount = CFArrayGetCount(proxies); |
for (proxyIndex = 0; proxyIndex < proxyCount; proxyIndex++) { |
CFDictionaryRef thisProxy; |
CFStringRef proxyType; |
thisProxy = (CFDictionaryRef) CFArrayGetValueAtIndex(proxies, proxyIndex); |
assert(thisProxy != NULL); |
assert(CFGetTypeID(thisProxy) == CFDictionaryGetTypeID()); |
proxyType = (CFStringRef) CFDictionaryGetValue(thisProxy, kCFProxyTypeKey); |
assert(proxyType != NULL); |
assert(CFGetTypeID(proxyType) == CFStringGetTypeID()); |
if ( ! CFEqual(proxyType, kCFProxyTypeAutoConfigurationURL) ) { |
// If it's not a PAC proxy, just copy it across. |
CFArrayAppendValue(expandedProxies, thisProxy); |
} else { |
CFRunLoopSourceRef rls; |
CFURLRef scriptURL; |
CFTypeRef result; |
CFStreamClientContext context = { 0, &result, NULL, NULL, NULL }; |
// If it is a PAC proxy, expand it and copy the results across. |
result = NULL; |
scriptURL = CFDictionaryGetValue(thisProxy, kCFProxyAutoConfigurationURLKey); |
assert(scriptURL != NULL); |
assert(CFGetTypeID(scriptURL) == CFURLGetTypeID()); |
// We don't need to work around <rdar://problem/5530166> because |
// we know that our caller has called CFNetworkCopyProxiesForURL. |
rls = CFNetworkExecuteProxyAutoConfigurationURL(scriptURL, url, ResultCallback, &context); |
if (rls == NULL) { |
err = coreFoundationUnknownErr; |
} |
// Despite the fact that CFNetworkExecuteProxyAutoConfigurationURL has |
// neither a "Create" nor a "Copy" in the name, we are required to |
// release the return CFRunLoopSourceRef <rdar://problem/5533931>. |
if (err == noErr) { |
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kPrivateRunLoopMode); |
CFRunLoopRunInMode(kPrivateRunLoopMode, 1.0e10, false); |
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), rls, kPrivateRunLoopMode); |
// Once the runloop returns, we should have either an error result or a |
// proxies array result. Do the appropriate thing with that result. |
assert(result != NULL); |
if ( CFGetTypeID(result) == CFErrorGetTypeID() ) { |
if ( CFEqual(CFErrorGetDomain( (CFErrorRef) result ), kCFErrorDomainOSStatus) ) { |
err = CFErrorGetCode( (CFErrorRef) result ); |
} else { |
err = coreFoundationUnknownErr; |
} |
} else if ( CFGetTypeID(result) == CFArrayGetTypeID() ) { |
CFArrayAppendArray(expandedProxies, result, CFRangeMake(0, CFArrayGetCount(result))); |
} else { |
assert(false); |
err = kernelTimeoutErr; |
} |
} |
CFQRelease(result); |
CFQRelease(rls); |
} |
} |
} |
// Clean up. |
if (err == noErr) { |
*expandedProxiesPtr = expandedProxies; |
} else { |
CFQRelease(expandedProxies); |
} |
assert( (err == noErr) == (*expandedProxiesPtr != NULL) ); |
return err; |
} |
static OSStatus ProxyAwareGetURL(const char *urlStr) |
// Implements the "proxyAwareGetURL" command. |
{ |
OSStatus err; |
CFURLRef url; |
CFDictionaryRef proxySettings; |
CFArrayRef proxies; |
CFArrayRef proxiesToTry; |
url = NULL; |
proxySettings = NULL; |
proxies = NULL; |
proxiesToTry = NULL; |
// Create a URL from the argument C string, and verify that it's an plain ol' |
// HTTP request. |
err = noErr; |
url = CFURLCreateWithBytes(NULL, (const UInt8 *) urlStr, strlen(urlStr), kCFStringEncodingUTF8, NULL); |
if (url == NULL) { |
err = coreFoundationUnknownErr; |
} |
if (err == noErr) { |
CFStringRef scheme; |
scheme = CFURLCopyScheme(url); |
assert(scheme != NULL); |
if ( CFStringCompare(scheme, CFSTR("HTTP"), kCFCompareCaseInsensitive) != kCFCompareEqualTo ) { |
err = unimpErr; |
} |
CFQRelease(scheme); |
} |
// Get the default proxies dictionary from CF. |
if (err == noErr) { |
proxySettings = SCDynamicStoreCopyProxies(NULL); |
if (proxySettings == NULL) { |
err = coreFoundationUnknownErr; |
} |
} |
// Call CFNetworkCopyProxiesForURL to get the proxy list. Then expand |
// any PAC-based proxies. |
if (err == noErr) { |
proxies = CFNetworkCopyProxiesForURL(url, proxySettings); |
if (proxies == NULL) { |
err = coreFoundationUnknownErr; |
} |
} |
if (err == noErr) { |
err = CreateProxyListWithExpandedPACProxies(url, proxies, &proxiesToTry); |
} |
// Finally, try to do get the resource using the various proxies we have |
// available. |
if (err == noErr) { |
CFIndex proxyCount; |
CFIndex proxyIndex; |
proxyCount = CFArrayGetCount(proxiesToTry); |
for (proxyIndex = 0; proxyIndex < proxyCount; proxyIndex++) { |
CFDictionaryRef thisProxy; |
CFStringRef proxyType; |
thisProxy = (CFDictionaryRef) CFArrayGetValueAtIndex(proxiesToTry, proxyIndex); |
assert(thisProxy != NULL); |
assert(CFGetTypeID(thisProxy) == CFDictionaryGetTypeID()); |
proxyType = CFDictionaryGetValue(thisProxy, kCFProxyTypeKey); |
assert(proxyType != NULL); |
assert(CFGetTypeID(proxyType) == CFStringGetTypeID()); |
if ( CFEqual(proxyType, kCFProxyTypeNone) ) { |
fprintf(stdout, "%ld Trying direct access (%s)\n", (long) proxyIndex, EasyToUseButNotThreadSafeCStringForCFString(proxyType)); |
err = HTTPGetAndPrintFromURL(url); |
} else if ( CFEqual(proxyType, kCFProxyTypeHTTP) ) { |
fprintf(stdout, "%ld Trying HTTP proxy (%s)\n", (long) proxyIndex, EasyToUseButNotThreadSafeCStringForCFString(proxyType)); |
err = HTTPGetAndPrintFromProxy(url, thisProxy); |
} else { |
fprintf(stdout, "%ld Skipping unsupported proxy (%s)\n", (long) proxyIndex, EasyToUseButNotThreadSafeCStringForCFString(proxyType)); |
err = unimpErr; |
} |
if (err == noErr) { |
break; |
} else if (err != unimpErr) { |
fprintf(stdout, " Failed with error %ld\n", (long) err); |
} |
} |
} |
// Clean up. |
CFQRelease(proxiesToTry); |
CFQRelease(proxies); |
CFQRelease(proxySettings); |
CFQRelease(url); |
return err; |
} |
#pragma mark ***** main |
int main(int argc, char **argv) |
{ |
int retVal; |
OSStatus err; |
// Parse the arguments and dispatch to the various command routines. |
err = noErr; |
retVal = EXIT_SUCCESS; |
if (argc < 2) { |
retVal = EXIT_FAILURE; |
} else { |
if (strcasecmp(argv[1], "proxiesForURL") == 0) { |
if (argc != 3) { |
retVal = EXIT_FAILURE; |
} else { |
err = ProxiesForURL(argv[2]); |
} |
} else if (strcasecmp(argv[1], "proxiesForURLUsingScript") == 0) { |
if (argc != 4) { |
retVal = EXIT_FAILURE; |
} else { |
err = ProxiesForURLUsingScript(argv[2], argv[3]); |
} |
} else if (strcasecmp(argv[1], "proxiesForURLUsingScriptURL") == 0) { |
if (argc != 4) { |
retVal = EXIT_FAILURE; |
} else { |
err = ProxiesForURLUsingScriptURL(argv[2], argv[3]); |
} |
} else if (strcasecmp(argv[1], "proxyAwareGetURL") == 0) { |
if (argc != 3) { |
retVal = EXIT_FAILURE; |
} else { |
err = ProxyAwareGetURL(argv[2]); |
} |
} else { |
retVal = EXIT_FAILURE; |
} |
} |
// Handle any errors. |
if (err != noErr) { |
fprintf(stderr, "Failed with error %ld.\n", (long) err); |
retVal = EXIT_FAILURE; |
} |
if ( (retVal == EXIT_FAILURE) && (err == noErr) ) { |
fprintf(stderr, "usage: %s proxiesForURL targetURL\n", getprogname()); |
fprintf(stderr, " %*s proxiesForURLUsingScript targetURL scriptPath\n", (int) strlen(getprogname()), ""); |
fprintf(stderr, " %*s proxiesForURLUsingScriptURL targetURL scriptURL\n", (int) strlen(getprogname()), ""); |
fprintf(stderr, " %*s proxyAwareGetURL targetURL\n", (int) strlen(getprogname()), ""); |
} |
return retVal; |
} |
Copyright © 2007 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2007-10-29