diff --git a/omega-pac/src/profiles.coffee b/omega-pac/src/profiles.coffee index 3b49c00a1..d61b52c90 100644 --- a/omega-pac/src/profiles.coffee +++ b/omega-pac/src/profiles.coffee @@ -79,12 +79,23 @@ module.exports = exports = port: port } + normalizeProxyHost: (host) -> + return host unless typeof host == 'string' + return host unless /[^\x00-\x7f]/.test(host) + try + normalized = new URL('http://' + host).hostname + return normalized if normalized + catch _ + return host + host + pacResult: (proxy) -> if proxy + host = exports.normalizeProxyHost(proxy.host) if checkNeedFixForSocks5(proxy) - "SOCKS5 #{proxy.host}:#{proxy.port}; SOCKS #{proxy.host}:#{proxy.port}" + "SOCKS5 #{host}:#{proxy.port}; SOCKS #{host}:#{proxy.port}" else - "#{exports.pacProtocols[proxy.scheme]} #{proxy.host}:#{proxy.port}" + "#{exports.pacProtocols[proxy.scheme]} #{host}:#{proxy.port}" else 'DIRECT' diff --git a/omega-pac/test/profiles.coffee b/omega-pac/test/profiles.coffee index 4a3974212..7f1c58633 100644 --- a/omega-pac/test/profiles.coffee +++ b/omega-pac/test/profiles.coffee @@ -46,6 +46,10 @@ describe 'Profiles', -> it 'should return a valid PAC result for a proxy', -> proxy = {scheme: "http", host: "127.0.0.1", port: 8888} Profiles.pacResult(proxy).should.equal("PROXY 127.0.0.1:8888") + it 'should normalize IDN proxy hosts to ASCII', -> + proxy = {scheme: "https", host: "\u30b7.\u30b7", port: 443} + Profiles.pacResult(proxy).should.equal("HTTPS xn--xck.xn--xck:443") + proxy.host.should.equal("\u30b7.\u30b7") it 'should return special compatible result for SOCKS5', -> proxy = {scheme: "socks5", host: "127.0.0.1", port: 8888} compatibleResult = "SOCKS5 127.0.0.1:8888; SOCKS 127.0.0.1:8888" diff --git a/omega-target-chromium-extension/src/js/omega_webext_proxy_script.js b/omega-target-chromium-extension/src/js/omega_webext_proxy_script.js index 0a653c73d..17c509657 100644 --- a/omega-target-chromium-extension/src/js/omega_webext_proxy_script.js +++ b/omega-target-chromium-extension/src/js/omega_webext_proxy_script.js @@ -40,7 +40,7 @@ FindProxyForURL = (function () { if (proxy && !state.useLegacyStringReturn) { var proxyInfo = { type: proxy.scheme, - host: proxy.host, + host: OmegaPac.Profiles.normalizeProxyHost(proxy.host), port: proxy.port, }; if (proxyInfo.type === 'socks5') { diff --git a/omega-target-chromium-extension/src/module/proxy/proxy_auth.coffee b/omega-target-chromium-extension/src/module/proxy/proxy_auth.coffee index 3f9acc2a6..58cfc16ad 100644 --- a/omega-target-chromium-extension/src/module/proxy/proxy_auth.coffee +++ b/omega-target-chromium-extension/src/module/proxy/proxy_auth.coffee @@ -31,7 +31,9 @@ module.exports = class ProxyAuth ) @listening = true - _keyForProxy: (proxy) -> "#{proxy.host.toLowerCase()}:#{proxy.port}" + _keyForProxy: (proxy) -> + host = OmegaPac.Profiles.normalizeProxyHost(proxy.host) + "#{host.toLowerCase()}:#{proxy.port}" setProxies: (profiles) -> @_proxies = {} @_fallbacks = [] diff --git a/omega-target-chromium-extension/src/module/proxy/proxy_impl_firefox.coffee b/omega-target-chromium-extension/src/module/proxy/proxy_impl_firefox.coffee index d53799789..f34ec1af5 100644 --- a/omega-target-chromium-extension/src/module/proxy/proxy_impl_firefox.coffee +++ b/omega-target-chromium-extension/src/module/proxy/proxy_impl_firefox.coffee @@ -1,4 +1,5 @@ OmegaTarget = require('omega-target') +OmegaPac = OmegaTarget.OmegaPac # The browser only accepts native promises as onRequest return values. # DO NOT USE Bluebird Promises here! NativePromise = Promise ? null @@ -96,15 +97,15 @@ class FirefoxProxyImpl extends ProxyImpl proxyInfo: (proxy, auth) -> proxyInfo = type: proxy.scheme - host: proxy.host + host: OmegaPac.Profiles.normalizeProxyHost(proxy.host) port: proxy.port if proxyInfo.type == 'socks5' # MOZ: SOCKS5 proxies should be specified as "type": "socks". - # https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/proxy/ProxyInfo + # See MDN WebExtensions proxy.ProxyInfo. proxyInfo.type = 'socks' if auth # Username & password here are only available for SOCKS5. - # https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/proxy/ProxyInfo + # See MDN WebExtensions proxy.ProxyInfo. # HTTP proxy auth must be handled via webRequest.onAuthRequired. proxyInfo.username = auth.username proxyInfo.password = auth.password diff --git a/omega-target-chromium-extension/src/module/proxy/proxy_impl_listener.coffee b/omega-target-chromium-extension/src/module/proxy/proxy_impl_listener.coffee index cfccc4e64..f8c3a6279 100644 --- a/omega-target-chromium-extension/src/module/proxy/proxy_impl_listener.coffee +++ b/omega-target-chromium-extension/src/module/proxy/proxy_impl_listener.coffee @@ -1,4 +1,5 @@ OmegaTarget = require('omega-target') +OmegaPac = OmegaTarget.OmegaPac # The browser only accepts native promises as onRequest return values. # DO NOT USE Bluebird Promises here! NativePromise = Promise ? null @@ -60,15 +61,15 @@ class ListenerProxyImpl extends ProxyImpl proxyInfo: (proxy, auth) -> proxyInfo = type: proxy.scheme - host: proxy.host + host: OmegaPac.Profiles.normalizeProxyHost(proxy.host) port: proxy.port if proxyInfo.type == 'socks5' # MOZ: SOCKS5 proxies should be specified as "type": "socks". - # https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/proxy/ProxyInfo + # See MDN WebExtensions proxy.ProxyInfo. proxyInfo.type = 'socks' if auth # Username & password here are only available for SOCKS5. - # https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/proxy/ProxyInfo + # See MDN WebExtensions proxy.ProxyInfo. # HTTP proxy auth must be handled via webRequest.onAuthRequired. proxyInfo.username = auth.username proxyInfo.password = auth.password diff --git a/omega-target-chromium-extension/src/module/proxy/proxy_impl_settings.coffee b/omega-target-chromium-extension/src/module/proxy/proxy_impl_settings.coffee index d38e3dcbf..3be4b91ea 100644 --- a/omega-target-chromium-extension/src/module/proxy/proxy_impl_settings.coffee +++ b/omega-target-chromium-extension/src/module/proxy/proxy_impl_settings.coffee @@ -81,21 +81,22 @@ class SettingsProxyImpl extends ProxyImpl protocols = ['proxyForHttp', 'proxyForHttps', 'proxyForFtp'] protocolProxySet = false for protocol in protocols when profile[protocol]? - rules[protocol] = profile[protocol] + rules[protocol] = @_normalizeProxyHost(profile[protocol]) protocolProxySet = true if profile.fallbackProxy + fallbackProxy = @_normalizeProxyHost(profile.fallbackProxy) if profile.fallbackProxy.scheme == 'http' # Chromium does not allow HTTP proxies in 'fallbackProxy'. if not protocolProxySet # Use 'singleProxy' if no proxy is configured for other protocols. - rules['singleProxy'] = profile.fallbackProxy + rules['singleProxy'] = fallbackProxy else # Try to set the proxies of all possible protocols. for protocol in protocols - rules[protocol] ?= JSON.parse(JSON.stringify(profile.fallbackProxy)) + rules[protocol] ?= JSON.parse(JSON.stringify(fallbackProxy)) else - rules['fallbackProxy'] = profile.fallbackProxy + rules['fallbackProxy'] = fallbackProxy else if not protocolProxySet config['mode'] = 'direct' @@ -105,6 +106,12 @@ class SettingsProxyImpl extends ProxyImpl bypassList.push(@_formatBypassItem(condition)) config['rules'] = rules return config + _normalizeProxyHost: (proxy) -> + host = OmegaPac.Profiles.normalizeProxyHost(proxy.host) + return proxy if host == proxy.host + proxy = JSON.parse(JSON.stringify(proxy)) + proxy.host = host + proxy _formatBypassItem: (condition) -> str = OmegaPac.Conditions.str(condition) i = str.indexOf(' ') diff --git a/omega-target-chromium-extension/test/proxy_hosts.coffee b/omega-target-chromium-extension/test/proxy_hosts.coffee new file mode 100644 index 000000000..4da2b8467 --- /dev/null +++ b/omega-target-chromium-extension/test/proxy_hosts.coffee @@ -0,0 +1,57 @@ +chai = require 'chai' +should = chai.should() + +describe 'Proxy host normalization', -> + SettingsProxyImpl = + require '../src/module/proxy/proxy_impl_settings' + ListenerProxyImpl = + require '../src/module/proxy/proxy_impl_listener' + FirefoxProxyImpl = + require '../src/module/proxy/proxy_impl_firefox' + ProxyAuth = require '../src/module/proxy/proxy_auth' + + idnHost = '\u30b7.\u30b7' + idnHostAscii = 'xn--xck.xn--xck' + idnFallbackHost = 'b\u00fccher.example' + idnFallbackHostAscii = 'xn--bcher-kva.example' + + it 'should normalize fixed server proxy hosts without mutating profiles', -> + log = + error: -> null + impl = new SettingsProxyImpl(log) + profile = + profileType: 'FixedProfile' + bypassList: [] + proxyForHttp: + scheme: 'http' + host: idnHost + port: 8080 + fallbackProxy: + scheme: 'socks5' + host: idnFallbackHost + port: 1080 + + config = impl._fixedProfileConfig(profile) + + config.rules.proxyForHttp.host.should.equal(idnHostAscii) + config.rules.fallbackProxy.host.should.equal(idnFallbackHostAscii) + profile.proxyForHttp.host.should.equal(idnHost) + profile.fallbackProxy.host.should.equal(idnFallbackHost) + + it 'should normalize listener proxy info hosts', -> + proxy = + scheme: 'https' + host: idnHost + port: 8443 + + ListenerProxyImpl::proxyInfo(proxy)[0].host.should.equal(idnHostAscii) + FirefoxProxyImpl::proxyInfo(proxy)[0].host.should.equal(idnHostAscii) + + it 'should normalize proxy auth lookup keys', -> + log = + error: -> null + log: -> null + auth = new ProxyAuth(log) + key = auth._keyForProxy(host: idnHost, port: 3128) + + key.should.equal(idnHostAscii + ':3128')