diff --git a/omega-target-chromium-extension/src/coffee/background.coffee b/omega-target-chromium-extension/src/coffee/background.coffee index 68c0e45f..2572540d 100644 --- a/omega-target-chromium-extension/src/coffee/background.coffee +++ b/omega-target-chromium-extension/src/coffee/background.coffee @@ -179,7 +179,10 @@ if chrome?.storage?.sync or browser?.storage?.sync sync.enabled = false sync.transformValue = OmegaTargetCurrent.Options.transformValueForSync -options = new OmegaTargetCurrent.Options(null, storage, state, Log, sync) +proxyImpl = OmegaTargetCurrent.proxy.getProxyImpl(Log) +state.set({proxyImplFeatures: proxyImpl.features}) +options = new OmegaTargetCurrent.Options(null, storage, state, Log, sync, + proxyImpl) options.externalApi = new OmegaTargetCurrent.ExternalApi(options) options.externalApi.listen() @@ -218,7 +221,7 @@ options._inspect = new OmegaTargetCurrent.Inspect (url, tab) -> options.setProxyNotControllable(null) timeout = null -options.watchProxyChange (details) -> +proxyImpl.watchProxyChange (details) -> return if options.externalApi.disabled return unless details notControllableBefore = options.proxyNotControllable() diff --git a/omega-target-chromium-extension/src/module/index.coffee b/omega-target-chromium-extension/src/module/index.coffee index cc69cf99..cedc3390 100644 --- a/omega-target-chromium-extension/src/module/index.coffee +++ b/omega-target-chromium-extension/src/module/index.coffee @@ -7,6 +7,7 @@ module.exports = WebRequestMonitor: require('./web_request_monitor') Inspect: require('./inspect') Url: require('url') + proxy: require('./proxy') for name, value of require('omega-target') module.exports[name] ?= value diff --git a/omega-target-chromium-extension/src/module/options.coffee b/omega-target-chromium-extension/src/module/options.coffee index dbdbf66e..efc4d77b 100644 --- a/omega-target-chromium-extension/src/module/options.coffee +++ b/omega-target-chromium-extension/src/module/options.coffee @@ -2,9 +2,7 @@ OmegaTarget = require('omega-target') OmegaPac = OmegaTarget.OmegaPac Promise = OmegaTarget.Promise querystring = require('querystring') -chromeApiPromisify = require('./chrome_api').chromeApiPromisify parseExternalProfile = require('./parse_external_profile') -ProxyAuth = require('./proxy_auth') WebRequestMonitor = require('./web_request_monitor') ChromePort = require('./chrome_port') fetchUrl = require('./fetch_url') @@ -73,202 +71,6 @@ class ChromeOptions extends OmegaTarget.Options chrome.browserAction.setBadgeText?(text: '') return - _formatBypassItem: (condition) -> - str = OmegaPac.Conditions.str(condition) - i = str.indexOf(' ') - return str.substr(i + 1) - _fixedProfileConfig: (profile) -> - config = {} - config['mode'] = 'fixed_servers' - rules = {} - protocols = ['proxyForHttp', 'proxyForHttps', 'proxyForFtp'] - protocolProxySet = false - for protocol in protocols when profile[protocol]? - rules[protocol] = profile[protocol] - protocolProxySet = true - - if 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 - else - # Try to set the proxies of all possible protocols. - for protocol in protocols - rules[protocol] ?= JSON.parse(JSON.stringify(profile.fallbackProxy)) - else - rules['fallbackProxy'] = profile.fallbackProxy - else if not protocolProxySet - config['mode'] = 'direct' - - if config['mode'] != 'direct' - rules['bypassList'] = bypassList = [] - for condition in profile.bypassList - bypassList.push(@_formatBypassItem(condition)) - config['rules'] = rules - return config - - _proxyChangeWatchers: null - _proxyChangeListener: null - watchProxyChange: (callback) -> - @_proxyChangeWatchers = [] - if not @_proxyChangeListener? - @_proxyChangeListener = (details) => - for watcher in @_proxyChangeWatchers - watcher(details) - if chrome?.proxy?.settings?.onChange? - chrome.proxy.settings.onChange.addListener @_proxyChangeListener - @_proxyChangeWatchers.push(callback) - applyProfileProxy: (profile, meta) -> - if browser?.proxy?.register? or browser?.proxy?.registerProxyScript? - return @applyProfileProxyScript(profile, meta) - else if chrome?.proxy?.settings? - return @applyProfileProxySettings(profile, meta) - else - ex = new Error('Your browser does not support proxy settings!') - return Promise.reject ex - applyProfileProxySettings: (profile, meta) -> - meta ?= profile - if profile.profileType == 'SystemProfile' - # Clear proxy settings, returning proxy control to Chromium. - return chromeApiPromisify(chrome.proxy.settings, 'clear')({}).then => - chrome.proxy.settings.get {}, @_proxyChangeListener - return - config = {} - if profile.profileType == 'DirectProfile' - config['mode'] = 'direct' - else if profile.profileType == 'PacProfile' - config['mode'] = 'pac_script' - - config['pacScript'] = - if !profile.pacScript || OmegaPac.Profiles.isFileUrl(profile.pacUrl) - url: profile.pacUrl - mandatory: true - else - data: OmegaPac.PacGenerator.ascii(profile.pacScript) - mandatory: true - else if profile.profileType == 'FixedProfile' - config = @_fixedProfileConfig(profile) - else - config['mode'] = 'pac_script' - config['pacScript'] = - data: null - mandatory: true - setPacScript = @pacForProfile(profile).then (script) -> - profileName = OmegaPac.PacGenerator.ascii(JSON.stringify(meta.name)) - profileName = profileName.replace(/\*/g, '\\u002a') - profileName = profileName.replace(/\\/g, '\\u002f') - prefix = "/*OmegaProfile*#{profileName}*#{meta.revision}*/" - config['pacScript'].data = prefix + script - return - setPacScript ?= Promise.resolve() - setPacScript.then(=> - @_proxyAuth ?= new ProxyAuth(this) - @_proxyAuth.listen() - @_proxyAuth.setProxies(@_watchingProfiles) - chromeApiPromisify(chrome.proxy.settings, 'set')({value: config}) - ).then => - chrome.proxy.settings.get {}, @_proxyChangeListener - return - - _proxyScriptUrl: 'js/omega_webext_proxy_script.min.js' - _proxyScriptDisabled: false - applyProfileProxyScript: (profile, state) -> - state = state ? {} - state.currentProfileName = profile.name - if profile.name == '' - state.tempProfile = @_tempProfile - if profile.profileType == 'SystemProfile' - # MOZ: SystemProfile cannot be done now due to lack of "PASS" support. - # https://bugzilla.mozilla.org/show_bug.cgi?id=1319634 - # In the mean time, let's just unregister the script. - if browser.proxy.unregister? - browser.proxy.unregister() - else - # Some older browers may not ship with .unregister API. - # In that case, let's just set an invalid script to unregister it. - browser.proxy.registerProxyScript('js/omega_invalid_proxy_script.js') - @_proxyScriptDisabled = true - else - @_proxyScriptState = state - Promise.all([ - browser.runtime.getBrowserInfo(), - @_initWebextProxyScript(), - ]).then ([info]) => - if info.vendor == 'Mozilla' and info.buildID < '20170918220054' - # MOZ: Legacy proxy support expects PAC-like string return type. - # TODO(catus): Remove support for string return type. - @log.error( - 'Your browser is outdated! SOCKS5 DNS/Auth unsupported! ' + - "Please update your browser ASAP! (Current Build #{info.buildID})") - @_proxyScriptState.useLegacyStringReturn = true - @_proxyScriptStateChanged() - @_proxyAuth ?= new ProxyAuth(this) - @_proxyAuth.listen() - @_proxyAuth.setProxies(@_watchingProfiles) - return Promise.resolve() - - _proxyScriptInitialized: false - _proxyScriptState: {} - _initWebextProxyScript: -> - if not @_proxyScriptInitialized - browser.proxy.onProxyError.addListener (err) => - if err?.message? - if err.message.indexOf('Invalid Proxy Rule: DIRECT') >= 0 - # DIRECT cannot be parsed in Mozilla earlier due to a bug. Even - # though it throws, it actually falls back to direct connection - # so it works. - # https://bugzilla.mozilla.org/show_bug.cgi?id=1355198 - return - if err.message.indexOf('Return type must be a string') >= 0 - # MOZ: Legacy proxy support expects PAC-like string return type. - # TODO(catus): Remove support for string return type. - # - @log.error( - 'Your browser is outdated! SOCKS5 DNS/Auth unsupported! ' + - 'Please update your browser ASAP!') - @_proxyScriptState.useLegacyStringReturn = true - @_proxyScriptStateChanged() - return - @log.error(err) - browser.runtime.onMessage.addListener (message) => - return unless message.event == 'proxyScriptLog' - if message.level == 'error' - @log.error(message) - else if message.level == 'warn' - @log.error(message) - else - @log.log(message) - - if not @_proxyScriptInitialized or @_proxyScriptDisabled - promise = new Promise (resolve) -> - onMessage = (message) -> - return unless message.event == 'proxyScriptLoaded' - resolve() - browser.runtime.onMessage.removeListener onMessage - return - browser.runtime.onMessage.addListener onMessage - # The API has been renamed to .register but for some old browsers' sake: - if browser.proxy.register? - browser.proxy.register(@_proxyScriptUrl) - else - browser.proxy.registerProxyScript(@_proxyScriptUrl) - @_proxyScriptDisabled = false - else - promise = Promise.resolve() - @_proxyScriptInitialized = true - return promise - - _proxyScriptStateChanged: -> - browser.runtime.sendMessage({ - event: 'proxyScriptStateChanged' - state: @_proxyScriptState - options: @_options - }, { - toProxyScript: true - }) - _quickSwitchInit: false _quickSwitchHandlerReady: false _quickSwitchCanEnable: false @@ -478,4 +280,3 @@ class ChromeOptions extends OmegaTarget.Options } module.exports = ChromeOptions - diff --git a/omega-target-chromium-extension/src/module/proxy/index.coffee b/omega-target-chromium-extension/src/module/proxy/index.coffee new file mode 100644 index 00000000..637cab89 --- /dev/null +++ b/omega-target-chromium-extension/src/module/proxy/index.coffee @@ -0,0 +1,10 @@ +ListenerProxyImpl = require('./proxy_impl_listener') +SettingsProxyImpl = require('./proxy_impl_settings') +ScriptProxyImpl = require('./proxy_impl_script') + +exports.proxyImpls = [ListenerProxyImpl, ScriptProxyImpl, SettingsProxyImpl] +exports.getProxyImpl = (log) -> + for Impl in exports.proxyImpls + if Impl.isSupported() + return new Impl(log) + throw new Error('Your browser does not support proxy settings!') diff --git a/omega-target-chromium-extension/src/module/proxy_auth.coffee b/omega-target-chromium-extension/src/module/proxy/proxy_auth.coffee similarity index 79% rename from omega-target-chromium-extension/src/module/proxy_auth.coffee rename to omega-target-chromium-extension/src/module/proxy/proxy_auth.coffee index 50aa764a..3f9acc2a 100644 --- a/omega-target-chromium-extension/src/module/proxy_auth.coffee +++ b/omega-target-chromium-extension/src/module/proxy/proxy_auth.coffee @@ -3,18 +3,18 @@ OmegaPac = OmegaTarget.OmegaPac Promise = OmegaTarget.Promise module.exports = class ProxyAuth - constructor: (options) -> + constructor: (log) -> @_requests = {} - @options = options + @log = log listening: false listen: -> return if @listening if not chrome.webRequest - @options.log.error('Proxy auth disabled! No webRequest permission.') + @log.error('Proxy auth disabled! No webRequest permission.') return if not chrome.webRequest.onAuthRequired - @options.log.error('Proxy auth disabled! onAuthRequired not available.') + @log.error('Proxy auth disabled! onAuthRequired not available.') return chrome.webRequest.onAuthRequired.addListener( @authHandler.bind(this) @@ -35,9 +35,7 @@ module.exports = class ProxyAuth setProxies: (profiles) -> @_proxies = {} @_fallbacks = [] - processProfile = (profile) => - profile = @options.profile(profile) - return unless profile?.auth + for profile in profiles when profile.auth for scheme in OmegaPac.Profiles.schemes when profile[scheme.prop] auth = profile.auth?[scheme.prop] continue unless auth @@ -59,13 +57,6 @@ module.exports = class ProxyAuth name: profile.name + '.' + 'all' }) - if Array.isArray(profiles) - for profile in profiles - processProfile(profile) - else - for _, profile of profiles - processProfile(profile) - _proxies: {} _fallbacks: [] _requests: null @@ -86,7 +77,7 @@ module.exports = class ProxyAuth proxy = list[req.authTries] else proxy = @_fallbacks[req.authTries - listLen] - @options.log.log('ProxyAuth', key, req.authTries, proxy?.name) + @log.log('ProxyAuth', key, req.authTries, proxy?.name) return {} unless proxy? req.authTries++ diff --git a/omega-target-chromium-extension/src/module/proxy/proxy_impl.coffee b/omega-target-chromium-extension/src/module/proxy/proxy_impl.coffee new file mode 100644 index 00000000..634625ec --- /dev/null +++ b/omega-target-chromium-extension/src/module/proxy/proxy_impl.coffee @@ -0,0 +1,41 @@ +OmegaTarget = require('omega-target') +Promise = OmegaTarget.Promise +ProxyAuth = require('./proxy_auth') + +class ProxyImpl + constructor: (log) -> + @log = log + @isSupported: -> false + applyProfile: (profile, meta) -> Promise.reject() + watchProxyChange: (callback) -> null + _profileNotFound: (name) -> + @log.error("Profile #{name} not found! Things may go very, very wrong.") + return OmegaPac.Profiles.create({ + name: name + profileType: 'VirtualProfile' + defaultProfileName: 'direct' + }) + setProxyAuth: (profile, options) -> + return Promise.try(=> + @_proxyAuth ?= new ProxyAuth(@log) + @_proxyAuth.listen() + referenced_profiles = [] + ref_set = OmegaPac.Profiles.allReferenceSet(profile, + options, profileNotFound: @_profileNotFound.bind(this)) + for own _, name of ref_set + referenced_profiles.push(OmegaPac.Profiles.byName(name, options)) + @_proxyAuth.setProxies(referenced_profiles) + ) + getProfilePacScript: (profile, meta, options) -> + meta ?= profile + ast = OmegaPac.PacGenerator.script(options, profile, + profileNotFound: @_profileNotFound.bind(this)) + ast = OmegaPac.PacGenerator.compress(ast) + script = OmegaPac.PacGenerator.ascii(ast.print_to_string()) + profileName = OmegaPac.PacGenerator.ascii(JSON.stringify(meta.name)) + profileName = profileName.replace(/\*/g, '\\u002a') + profileName = profileName.replace(/\\/g, '\\u002f') + prefix = "/*OmegaProfile*#{profileName}*#{meta.revision}*/" + return prefix + script + +module.exports = ProxyImpl 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 new file mode 100644 index 00000000..09467105 --- /dev/null +++ b/omega-target-chromium-extension/src/module/proxy/proxy_impl_listener.coffee @@ -0,0 +1,81 @@ +OmegaTarget = require('omega-target') +# The browser only accepts native promises as onRequest return values. +# DO NOT USE Bluebird Promises here! +NativePromise = Promise +ProxyImpl = require('./proxy_impl') + +class ListenerProxyImpl extends ProxyImpl + @isSupported: -> browser?.proxy?.onRequest? + features: ['fullUrl', 'socks5Auth'] + constructor: -> + super(arguments...) + @_optionsReady = new NativePromise (resolve) => + @_optionsReadyCallback = resolve + # We want to register listeners early so that it can start blocking requests + # when starting the browser & extension, returning correct results later. + @_initRequestListeners() + _initRequestListeners: -> + browser.proxy.onRequest.addListener(@onRequest.bind(this), + {urls: [""]}) + browser.proxy.onError.addListener(@onError.bind(this)) + watchProxyChange: (callback) -> null + applyProfile: (profile, state, options) -> + @_options = options + @_profile = profile + @_optionsReadyCallback?() + @_optionsReadyCallback = null + return @setProxyAuth(profile, options) + onRequest: (requestDetails) -> + # The browser only recognizes native promises return values, not Bluebird. + return NativePromise.resolve(@_optionsReady.then(=> + request = OmegaPac.Conditions.requestFromUrl(requestDetails.url) + profile = @_profile + while profile + result = OmegaPac.Profiles.match(profile, request) + if not result + switch profile.profileType + when 'DirectProfile' + return {type: 'direct'} + when 'SystemProfile' + # Returning undefined means using the default proxy from previous. + # https://hg.mozilla.org/mozilla-central/rev/9f0ee2f582a2#l1.337 + return undefined + else + throw new Error('Unsupported profile: ' + profile.profileType) + if Array.isArray(result) + proxy = result[2] + auth = result[3] + return @proxyInfo(proxy, auth) if proxy + next = result[0] + else if result.profileName + next = OmegaPac.Profiles.nameAsKey(result.profileName) + else + break + profile = OmegaPac.Profiles.byKey(next, @_options) + + throw new Error('Profile not found: ' + next) + )) + onError: (error) -> + @log.error(error) + proxyInfo: (proxy, auth) -> + proxyInfo = + type: proxy.scheme + host: 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 + 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 + # HTTP proxy auth must be handled via webRequest.onAuthRequired. + proxyInfo.username = auth.username + proxyInfo.password = auth.password + if proxyInfo.type == 'socks' or proxyInfo.type == 'socks4' + # Enable SOCKS remote DNS. + # TODO(catus): Maybe allow the users to configure this? + proxyInfo.proxyDNS = true + return [proxyInfo] + +module.exports = ListenerProxyImpl diff --git a/omega-target-chromium-extension/src/module/proxy/proxy_impl_script.coffee b/omega-target-chromium-extension/src/module/proxy/proxy_impl_script.coffee new file mode 100644 index 00000000..1c822242 --- /dev/null +++ b/omega-target-chromium-extension/src/module/proxy/proxy_impl_script.coffee @@ -0,0 +1,107 @@ +OmegaTarget = require('omega-target') +Promise = OmegaTarget.Promise +ProxyImpl = require('./proxy_impl') + +class ScriptProxyImpl extends ProxyImpl + @isSupported: -> + return browser?.proxy?.register? or browser?.proxy?.registerProxyScript? + features: ['socks5Auth'] + _proxyScriptUrl: 'js/omega_webext_proxy_script.min.js' + _proxyScriptDisabled: false + _proxyScriptInitialized: false + _proxyScriptState: {} + watchProxyChange: (callback) -> null + applyProfile: (profile, state, options) -> + @log.error( + 'Your browser is outdated! Full-URL based matching, etc. unsupported! ' + + "Please update your browser ASAP!") + state = state ? {} + @_options = options + state.currentProfileName = profile.name + if profile.name == '' + state.tempProfile = profile + if profile.profileType == 'SystemProfile' + # MOZ: SystemProfile cannot be done now due to lack of "PASS" support. + # https://bugzilla.mozilla.org/show_bug.cgi?id=1319634 + # In the mean time, let's just unregister the script. + if browser.proxy.unregister? + browser.proxy.unregister() + else + # Some older browers may not ship with .unregister API. + # In that case, let's just set an invalid script to unregister it. + browser.proxy.registerProxyScript('js/omega_invalid_proxy_script.js') + @_proxyScriptDisabled = true + else + @_proxyScriptState = state + Promise.all([ + browser.runtime.getBrowserInfo(), + @_initWebextProxyScript(), + ]).then ([info]) => + if info.vendor == 'Mozilla' and info.buildID < '20170918220054' + # MOZ: Legacy proxy support expects PAC-like string return type. + # TODO(catus): Remove support for string return type. + @log.error( + 'Your browser is outdated! SOCKS5 DNS/Auth unsupported! ' + + "Please update your browser ASAP! (Current Build #{info.buildID})") + @_proxyScriptState.useLegacyStringReturn = true + @_proxyScriptStateChanged() + return @setProxyAuth(profile, options) + _initWebextProxyScript: -> + if not @_proxyScriptInitialized + browser.proxy.onProxyError.addListener (err) => + if err?.message? + if err.message.indexOf('Invalid Proxy Rule: DIRECT') >= 0 + # DIRECT cannot be parsed in Mozilla earlier due to a bug. Even + # though it throws, it actually falls back to direct connection + # so it works. + # https://bugzilla.mozilla.org/show_bug.cgi?id=1355198 + return + if err.message.indexOf('Return type must be a string') >= 0 + # MOZ: Legacy proxy support expects PAC-like string return type. + # TODO(catus): Remove support for string return type. + # + @log.error( + 'Your browser is outdated! SOCKS5 DNS/Auth unsupported! ' + + 'Please update your browser ASAP!') + @_proxyScriptState.useLegacyStringReturn = true + @_proxyScriptStateChanged() + return + @log.error(err) + browser.runtime.onMessage.addListener (message) => + return unless message.event == 'proxyScriptLog' + if message.level == 'error' + @log.error(message) + else if message.level == 'warn' + @log.error(message) + else + @log.log(message) + + if not @_proxyScriptInitialized or @_proxyScriptDisabled + promise = new Promise (resolve) -> + onMessage = (message) -> + return unless message.event == 'proxyScriptLoaded' + resolve() + browser.runtime.onMessage.removeListener onMessage + return + browser.runtime.onMessage.addListener onMessage + # The API has been renamed to .register but for some old browsers' sake: + if browser.proxy.register? + browser.proxy.register(@_proxyScriptUrl) + else + browser.proxy.registerProxyScript(@_proxyScriptUrl) + @_proxyScriptDisabled = false + else + promise = Promise.resolve() + @_proxyScriptInitialized = true + return promise + + _proxyScriptStateChanged: -> + browser.runtime.sendMessage({ + event: 'proxyScriptStateChanged' + state: @_proxyScriptState + options: @_options + }, { + toProxyScript: true + }) + +module.exports = ScriptProxyImpl 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 new file mode 100644 index 00000000..a9d15cd6 --- /dev/null +++ b/omega-target-chromium-extension/src/module/proxy/proxy_impl_settings.coffee @@ -0,0 +1,90 @@ +sOmegaTarget = require('omega-target') +Promise = OmegaTarget.Promise +chromeApiPromisify = require('../chrome_api').chromeApiPromisify +ProxyImpl = require('./proxy_impl') + +class SettingsProxyImpl extends ProxyImpl + @isSupported: -> chrome?.proxy?.settings? + features: ['fullUrlHttp', 'pacScript', 'watchProxyChange'] + applyProfile: (profile, meta, options) -> + meta ?= profile + if profile.profileType == 'SystemProfile' + # Clear proxy settings, returning proxy control to Chromium. + return chromeApiPromisify(chrome.proxy.settings, 'clear')({}).then => + chrome.proxy.settings.get {}, @_proxyChangeListener + return + config = {} + if profile.profileType == 'DirectProfile' + config['mode'] = 'direct' + else if profile.profileType == 'PacProfile' + config['mode'] = 'pac_script' + + config['pacScript'] = + if !profile.pacScript || OmegaPac.Profiles.isFileUrl(profile.pacUrl) + url: profile.pacUrl + mandatory: true + else + data: OmegaPac.PacGenerator.ascii(profile.pacScript) + mandatory: true + else if profile.profileType == 'FixedProfile' + config = @_fixedProfileConfig(profile) + else + config['mode'] = 'pac_script' + config['pacScript'] = + mandatory: true + data: @getProfilePacScript(profile, meta, options) + return @setProxyAuth(profile, options).then(-> + return chromeApiPromisify(chrome.proxy.settings, 'set')({value: config}) + ).then(=> + chrome.proxy.settings.get {}, @_proxyChangeListener + return + ) + _fixedProfileConfig: (profile) -> + config = {} + config['mode'] = 'fixed_servers' + rules = {} + protocols = ['proxyForHttp', 'proxyForHttps', 'proxyForFtp'] + protocolProxySet = false + for protocol in protocols when profile[protocol]? + rules[protocol] = profile[protocol] + protocolProxySet = true + + if 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 + else + # Try to set the proxies of all possible protocols. + for protocol in protocols + rules[protocol] ?= JSON.parse(JSON.stringify(profile.fallbackProxy)) + else + rules['fallbackProxy'] = profile.fallbackProxy + else if not protocolProxySet + config['mode'] = 'direct' + + if config['mode'] != 'direct' + rules['bypassList'] = bypassList = [] + for condition in profile.bypassList + bypassList.push(@_formatBypassItem(condition)) + config['rules'] = rules + return config + _formatBypassItem: (condition) -> + str = OmegaPac.Conditions.str(condition) + i = str.indexOf(' ') + return str.substr(i + 1) + + _proxyChangeWatchers: null + _proxyChangeListener: (details) -> + for watcher in (@_proxyChangeWatchers ? []) + watcher(details) + watchProxyChange: (callback) -> + if not @_proxyChangeWatchers? + @_proxyChangeWatchers = [] + if chrome?.proxy?.settings?.onChange? + chrome.proxy.settings.onChange.addListener @_proxyChangeListener + @_proxyChangeWatchers.push(callback) + return + +module.exports = SettingsProxyImpl diff --git a/omega-target/src/options.coffee b/omega-target/src/options.coffee index 30c80bad..8209d68d 100644 --- a/omega-target/src/options.coffee +++ b/omega-target/src/options.coffee @@ -54,7 +54,7 @@ class Options value = profile return value - constructor: (options, @_storage, @_state, @log, @sync) -> + constructor: (options, @_storage, @_state, @log, @sync, @proxyImpl) -> @_options = {} @_tempProfileRules = {} @_tempProfileRulesByProfile = {} @@ -566,9 +566,10 @@ class Options @_watchingProfiles = OmegaPac.Profiles.allReferenceSet(@_tempProfile, @_options, profileNotFound: @_profileNotFound.bind(this)) - applyProxy = @applyProfileProxy(@_tempProfile, profile) + + applyProxy = @proxyImpl.applyProfile(@_tempProfile, profile, @_options) else - applyProxy = @applyProfileProxy(profile) + applyProxy = @proxyImpl.applyProfile(profile, profile, @_options) return applyProxy if options? and options.update == false @@ -598,16 +599,6 @@ class Options ### isSystem: -> @_isSystem - ###* - # Set proxy settings based on the given profile. - # In base class, this method is not implemented and will always reject. - # @param {{}} profile The profile to apply - # @param {{}=profile} meta The metadata of the profile, like name and revision - # @returns {Promise} A promise which is fulfilled when the proxy is set. - ### - applyProfileProxy: (profile, meta) -> - Promise.reject new Error('not implemented') - ###* # Called when current profile has changed. # In base class, this method is not implemented and will not do anything.