Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Proxy type kCFProxyTypeHTTPS expects to be HTTP proxy on macOS #48

Merged
merged 1 commit into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,15 @@ jobs:
fi
out=$(./proxycli resolve https://simple.com/)
echo "Proxy for https://simple.com/: $out"
if [ "$out" = "https://no-such-proxy:80" ]; then
if [ "$out" = "http://no-such-proxy:80" ]; then
echo "Proxy for https://simple.com/ set successfully"
else
echo "Proxy for https://simple.com/ set failed"
exit 1
fi
out=$(./proxycli resolve https://multi-legacy.com/)
echo "Proxy for https://multi-legacy.com/: $out"
if [ "$out" = "https://some-such-proxy:443,https://any-such-proxy:41" ]; then
if [ "$out" = "http://some-such-proxy:443,http://any-such-proxy:41" ]; then
echo "Proxy for https://multi-legacy.com/ set successfully"
else
echo "Proxy for https://multi-legacy.com/ set failed"
Expand Down
77 changes: 44 additions & 33 deletions resolver_mac.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,78 +33,89 @@ typedef struct proxy_resolver_mac_s {
char *list;
} proxy_resolver_mac_s;

static const char *proxy_resolver_mac_get_proxy_scheme(CFStringRef proxy_type) {
const char *scheme = NULL;
if (CFEqual(proxy_type, kCFProxyTypeNone))
scheme = "direct://";
else if (CFEqual(proxy_type, kCFProxyTypeHTTP))
scheme = "http://";
else if (CFEqual(proxy_type, kCFProxyTypeHTTPS))
// "HTTPS Proxy" on macOS means "use CONNENCT verb for a https:// URL", the proxy still should be an HTTP proxy.
Copy link
Owner

@nmoinvaz nmoinvaz Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this seems to suggest that macOS does not support HTTPS directive from PAC file. However, an HTTPS proxy can still probably be manually configured in settings dialog?

https://support.apple.com/guide/mac-help/change-proxy-settings-on-mac-mchlp2591/mac
image

Is that your take as well?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it would make sense it does not support HTTP/HTTPS directives because it uses multi-legacy.com in tests which I have commented in the pac file used in the test:

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK, it is first time mentioned in macOS Montgomery, but we are targeting macOS 10.13. It may change the behavior if we change the target. If it still returns CFProxyTypeHTTPS for the PROXY directive, it is useless for us.

scheme = "http://";
else if (CFEqual(proxy_type, kCFProxyTypeSOCKS))
scheme = "socks://";
else if (CFEqual(proxy_type, kCFProxyTypeFTP))
scheme = "ftp://";
return scheme;
}

static void proxy_resolver_mac_auto_config_result_callback(void *client, CFArrayRef proxy_array, CFErrorRef error) {
proxy_resolver_mac_s *proxy_resolver = (proxy_resolver_mac_s *)client;
if (error) {
// Get error code
proxy_resolver->error = CFErrorGetCode(error);
} else {
// Convert proxy array into PAC file return format
size_t proxy_count = CFArrayGetCount(proxy_array);
size_t max_list = proxy_count * MAX_PROXY_URL + 1;
const size_t proxy_count = CFArrayGetCount(proxy_array);
const size_t max_list = proxy_count * MAX_PROXY_URL + 1;
size_t list_len = 0;

proxy_resolver->list = (char *)calloc(max_list, sizeof(char));

// Enumerate through each proxy in the array
for (size_t i = 0; proxy_resolver->list && i < proxy_count; i++) {
for (size_t i = 0; proxy_resolver->list && i < proxy_count && list_len < max_list; i++) {
CFDictionaryRef proxy = CFArrayGetValueAtIndex(proxy_array, i);
CFStringRef proxy_type = (CFStringRef)CFDictionaryGetValue(proxy, kCFProxyTypeKey);

// Copy type of connection
if (CFEqual(proxy_type, kCFProxyTypeNone)) {
strncat(proxy_resolver->list, "direct://", max_list - list_len - 1);
list_len += 9;
} else if (CFEqual(proxy_type, kCFProxyTypeHTTP)) {
strncat(proxy_resolver->list, "http://", max_list - list_len - 1);
list_len += 7;
} else if (CFEqual(proxy_type, kCFProxyTypeHTTPS)) {
strncat(proxy_resolver->list, "https://", max_list - list_len - 1);
list_len += 8;
} else if (CFEqual(proxy_type, kCFProxyTypeSOCKS)) {
strncat(proxy_resolver->list, "socks://", max_list - list_len - 1);
list_len += 8;
} else if (CFEqual(proxy_type, kCFProxyTypeFTP)) {
strncat(proxy_resolver->list, "ftp://", max_list - list_len - 1);
list_len += 6;
const char *scheme = proxy_resolver_mac_get_proxy_scheme(proxy_type);
if (scheme) {
strncpy(proxy_resolver->list + list_len, scheme, max_list - list_len);
list_len += strlen(scheme);
} else {
LOG_WARN("Unknown proxy type encountered\n");
continue;
}

if (!CFEqual(proxy_type, kCFProxyTypeNone)) {
if (!CFEqual(proxy_type, kCFProxyTypeNone) && list_len < max_list) {
// Copy proxy host
CFStringRef host = (CFStringRef)CFDictionaryGetValue(proxy, kCFProxyHostNameKey);
if (host) {
if (host && list_len < max_list) {
const char *host_utf8 = CFStringGetCStringPtr(host, kCFStringEncodingUTF8);
if (host_utf8) {
strncat(proxy_resolver->list, host_utf8, max_list - list_len - 1);
strncpy(proxy_resolver->list + list_len, host_utf8, max_list - list_len);
list_len += strlen(host_utf8);
} else {
CFStringGetCString(host, proxy_resolver->list + list_len, max_list - list_len,
kCFStringEncodingUTF8);
} else if (CFStringGetCString(host, proxy_resolver->list + list_len, max_list - list_len,
kCFStringEncodingUTF8)) {
list_len = strlen(proxy_resolver->list);
} else {
list_len = max_list;
}
}
// Copy proxy port
CFNumberRef port = (CFNumberRef)CFDictionaryGetValue(proxy, kCFProxyPortNumberKey);
if (port) {
int64_t port_number = 0;
CFNumberGetValue(port, kCFNumberSInt64Type, &port_number);
snprintf(proxy_resolver->list + list_len, max_list - list_len, ":%" PRId64 "", port_number);
list_len = strlen(proxy_resolver->list);
if (port && list_len < max_list) {
int32_t port_number = 0;
CFNumberGetValue(port, kCFNumberSInt32Type, &port_number);
list_len +=
snprintf(proxy_resolver->list + list_len, max_list - list_len, ":%" PRId32, port_number);
}
}

if (i != proxy_count - 1) {
if (i != proxy_count - 1 && list_len < max_list) {
// Separate each proxy with a comma
strncat(proxy_resolver->list, ",", max_list - list_len - 1);
strncat(proxy_resolver->list, ",", max_list - list_len);
list_len++;
}
}

if (!proxy_resolver->list)
if (list_len >= max_list) {
proxy_resolver->error = ERANGE;
LOG_WARN("Proxy list limit exceeded\n");
free(proxy_resolver->list);
proxy_resolver->list = NULL;
} else if (!proxy_resolver->list) {
proxy_resolver->error = ENOMEM;
}
}

CFRunLoopStop(CFRunLoopGetCurrent());
Expand Down