Skip to content

Commit

Permalink
Support browser.proxy.onRequest. Fix #1456.
Browse files Browse the repository at this point in the history
This commit also refactors other implementations and moves them to
dedicated files, using feature detection to select one on runtime.
  • Loading branch information
FelisCatus committed Jul 9, 2018
1 parent 7a6cc98 commit 465c98f
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 229 deletions.
7 changes: 5 additions & 2 deletions omega-target-chromium-extension/src/coffee/background.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions omega-target-chromium-extension/src/module/index.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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
199 changes: 0 additions & 199 deletions omega-target-chromium-extension/src/module/options.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -478,4 +280,3 @@ class ChromeOptions extends OmegaTarget.Options
}

module.exports = ChromeOptions

10 changes: 10 additions & 0 deletions omega-target-chromium-extension/src/module/proxy/index.coffee
Original file line number Diff line number Diff line change
@@ -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!')
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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++
Expand Down
41 changes: 41 additions & 0 deletions omega-target-chromium-extension/src/module/proxy/proxy_impl.coffee
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 465c98f

Please sign in to comment.