mirror of
https://github.com/zero-peak/ZeroOmega.git
synced 2025-01-22 15:08:12 -05:00
Support browser.proxy.onRequest. Fix #1456.
This commit also refactors other implementations and moves them to dedicated files, using feature detection to select one on runtime.
This commit is contained in:
parent
7a6cc98ff7
commit
465c98f78a
@ -179,7 +179,10 @@ if chrome?.storage?.sync or browser?.storage?.sync
|
|||||||
sync.enabled = false
|
sync.enabled = false
|
||||||
sync.transformValue = OmegaTargetCurrent.Options.transformValueForSync
|
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 = new OmegaTargetCurrent.ExternalApi(options)
|
||||||
options.externalApi.listen()
|
options.externalApi.listen()
|
||||||
|
|
||||||
@ -218,7 +221,7 @@ options._inspect = new OmegaTargetCurrent.Inspect (url, tab) ->
|
|||||||
options.setProxyNotControllable(null)
|
options.setProxyNotControllable(null)
|
||||||
timeout = null
|
timeout = null
|
||||||
|
|
||||||
options.watchProxyChange (details) ->
|
proxyImpl.watchProxyChange (details) ->
|
||||||
return if options.externalApi.disabled
|
return if options.externalApi.disabled
|
||||||
return unless details
|
return unless details
|
||||||
notControllableBefore = options.proxyNotControllable()
|
notControllableBefore = options.proxyNotControllable()
|
||||||
|
@ -7,6 +7,7 @@ module.exports =
|
|||||||
WebRequestMonitor: require('./web_request_monitor')
|
WebRequestMonitor: require('./web_request_monitor')
|
||||||
Inspect: require('./inspect')
|
Inspect: require('./inspect')
|
||||||
Url: require('url')
|
Url: require('url')
|
||||||
|
proxy: require('./proxy')
|
||||||
|
|
||||||
for name, value of require('omega-target')
|
for name, value of require('omega-target')
|
||||||
module.exports[name] ?= value
|
module.exports[name] ?= value
|
||||||
|
@ -2,9 +2,7 @@ OmegaTarget = require('omega-target')
|
|||||||
OmegaPac = OmegaTarget.OmegaPac
|
OmegaPac = OmegaTarget.OmegaPac
|
||||||
Promise = OmegaTarget.Promise
|
Promise = OmegaTarget.Promise
|
||||||
querystring = require('querystring')
|
querystring = require('querystring')
|
||||||
chromeApiPromisify = require('./chrome_api').chromeApiPromisify
|
|
||||||
parseExternalProfile = require('./parse_external_profile')
|
parseExternalProfile = require('./parse_external_profile')
|
||||||
ProxyAuth = require('./proxy_auth')
|
|
||||||
WebRequestMonitor = require('./web_request_monitor')
|
WebRequestMonitor = require('./web_request_monitor')
|
||||||
ChromePort = require('./chrome_port')
|
ChromePort = require('./chrome_port')
|
||||||
fetchUrl = require('./fetch_url')
|
fetchUrl = require('./fetch_url')
|
||||||
@ -73,202 +71,6 @@ class ChromeOptions extends OmegaTarget.Options
|
|||||||
chrome.browserAction.setBadgeText?(text: '')
|
chrome.browserAction.setBadgeText?(text: '')
|
||||||
return
|
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
|
_quickSwitchInit: false
|
||||||
_quickSwitchHandlerReady: false
|
_quickSwitchHandlerReady: false
|
||||||
_quickSwitchCanEnable: false
|
_quickSwitchCanEnable: false
|
||||||
@ -478,4 +280,3 @@ class ChromeOptions extends OmegaTarget.Options
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ChromeOptions
|
module.exports = ChromeOptions
|
||||||
|
|
||||||
|
@ -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!')
|
@ -3,18 +3,18 @@ OmegaPac = OmegaTarget.OmegaPac
|
|||||||
Promise = OmegaTarget.Promise
|
Promise = OmegaTarget.Promise
|
||||||
|
|
||||||
module.exports = class ProxyAuth
|
module.exports = class ProxyAuth
|
||||||
constructor: (options) ->
|
constructor: (log) ->
|
||||||
@_requests = {}
|
@_requests = {}
|
||||||
@options = options
|
@log = log
|
||||||
|
|
||||||
listening: false
|
listening: false
|
||||||
listen: ->
|
listen: ->
|
||||||
return if @listening
|
return if @listening
|
||||||
if not chrome.webRequest
|
if not chrome.webRequest
|
||||||
@options.log.error('Proxy auth disabled! No webRequest permission.')
|
@log.error('Proxy auth disabled! No webRequest permission.')
|
||||||
return
|
return
|
||||||
if not chrome.webRequest.onAuthRequired
|
if not chrome.webRequest.onAuthRequired
|
||||||
@options.log.error('Proxy auth disabled! onAuthRequired not available.')
|
@log.error('Proxy auth disabled! onAuthRequired not available.')
|
||||||
return
|
return
|
||||||
chrome.webRequest.onAuthRequired.addListener(
|
chrome.webRequest.onAuthRequired.addListener(
|
||||||
@authHandler.bind(this)
|
@authHandler.bind(this)
|
||||||
@ -35,9 +35,7 @@ module.exports = class ProxyAuth
|
|||||||
setProxies: (profiles) ->
|
setProxies: (profiles) ->
|
||||||
@_proxies = {}
|
@_proxies = {}
|
||||||
@_fallbacks = []
|
@_fallbacks = []
|
||||||
processProfile = (profile) =>
|
for profile in profiles when profile.auth
|
||||||
profile = @options.profile(profile)
|
|
||||||
return unless profile?.auth
|
|
||||||
for scheme in OmegaPac.Profiles.schemes when profile[scheme.prop]
|
for scheme in OmegaPac.Profiles.schemes when profile[scheme.prop]
|
||||||
auth = profile.auth?[scheme.prop]
|
auth = profile.auth?[scheme.prop]
|
||||||
continue unless auth
|
continue unless auth
|
||||||
@ -59,13 +57,6 @@ module.exports = class ProxyAuth
|
|||||||
name: profile.name + '.' + 'all'
|
name: profile.name + '.' + 'all'
|
||||||
})
|
})
|
||||||
|
|
||||||
if Array.isArray(profiles)
|
|
||||||
for profile in profiles
|
|
||||||
processProfile(profile)
|
|
||||||
else
|
|
||||||
for _, profile of profiles
|
|
||||||
processProfile(profile)
|
|
||||||
|
|
||||||
_proxies: {}
|
_proxies: {}
|
||||||
_fallbacks: []
|
_fallbacks: []
|
||||||
_requests: null
|
_requests: null
|
||||||
@ -86,7 +77,7 @@ module.exports = class ProxyAuth
|
|||||||
proxy = list[req.authTries]
|
proxy = list[req.authTries]
|
||||||
else
|
else
|
||||||
proxy = @_fallbacks[req.authTries - listLen]
|
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?
|
return {} unless proxy?
|
||||||
req.authTries++
|
req.authTries++
|
@ -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
|
@ -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: ["<all_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
|
@ -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
|
@ -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
|
@ -54,7 +54,7 @@ class Options
|
|||||||
value = profile
|
value = profile
|
||||||
return value
|
return value
|
||||||
|
|
||||||
constructor: (options, @_storage, @_state, @log, @sync) ->
|
constructor: (options, @_storage, @_state, @log, @sync, @proxyImpl) ->
|
||||||
@_options = {}
|
@_options = {}
|
||||||
@_tempProfileRules = {}
|
@_tempProfileRules = {}
|
||||||
@_tempProfileRulesByProfile = {}
|
@_tempProfileRulesByProfile = {}
|
||||||
@ -566,9 +566,10 @@ class Options
|
|||||||
|
|
||||||
@_watchingProfiles = OmegaPac.Profiles.allReferenceSet(@_tempProfile,
|
@_watchingProfiles = OmegaPac.Profiles.allReferenceSet(@_tempProfile,
|
||||||
@_options, profileNotFound: @_profileNotFound.bind(this))
|
@_options, profileNotFound: @_profileNotFound.bind(this))
|
||||||
applyProxy = @applyProfileProxy(@_tempProfile, profile)
|
|
||||||
|
applyProxy = @proxyImpl.applyProfile(@_tempProfile, profile, @_options)
|
||||||
else
|
else
|
||||||
applyProxy = @applyProfileProxy(profile)
|
applyProxy = @proxyImpl.applyProfile(profile, profile, @_options)
|
||||||
|
|
||||||
return applyProxy if options? and options.update == false
|
return applyProxy if options? and options.update == false
|
||||||
|
|
||||||
@ -598,16 +599,6 @@ class Options
|
|||||||
###
|
###
|
||||||
isSystem: -> @_isSystem
|
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.
|
# Called when current profile has changed.
|
||||||
# In base class, this method is not implemented and will not do anything.
|
# In base class, this method is not implemented and will not do anything.
|
||||||
|
Loading…
Reference in New Issue
Block a user