[tor-commits] [meek/webextension] Allow specifying a proxy.

dcf at torproject.org dcf at torproject.org
Tue Feb 19 08:11:44 UTC 2019


commit f9c9f5aed26d3e01f766668da40bbed97984f0f7
Author: David Fifield <david at bamsoftware.com>
Date:   Tue Feb 19 00:44:15 2019 -0700

    Allow specifying a proxy.
    
    Just like with headers, we can only control the proxy through a global
    event listener, namely proxy.onRequest. We use the same scheme of
    locking modifications to the events so that only one request at a time
    is affected.
---
 webextension/background.js  | 68 ++++++++++++++++++++++++++++++++++++++++-----
 webextension/manifest.json  |  1 +
 webextension/native/main.go |  2 +-
 3 files changed, 63 insertions(+), 8 deletions(-)

diff --git a/webextension/background.js b/webextension/background.js
index 5aab472..3b24a37 100644
--- a/webextension/background.js
+++ b/webextension/background.js
@@ -77,6 +77,31 @@ function base64_encode(dec_buf) {
     return btoa(dec_str);
 }
 
+// Return a proxy.ProxyInfo according to the given specification.
+//
+// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo
+// The specification may look like:
+//   undefined
+//   {"type": "http", "host": "example.com", "port": 8080}
+//   {"type": "socks5", "host": "example.com", "port": 1080}
+//   {"type": "socks4a", "host": "example.com", "port": 1080}
+function makeProxyInfo(spec) {
+    if (spec == null) {
+        return {type: "direct"};
+    }
+    switch (spec.type) {
+        case "http":
+            return {type: "http", host: spec.host, port: spec.port};
+        // What tor calls "socks5", WebExtension calls "socks".
+        case "socks5":
+            return {type: "socks", host: spec.host, port: spec.port, proxyDNS: true};
+        // What tor calls "socks4a", WebExtension calls "socks4".
+        case "socks4a":
+            return {type: "socks4", host: spec.host, port: spec.port, proxyDNS: true};
+    };
+    throw new Error(`unknown proxy type ${spec.type}`);
+}
+
 // A Mutex's lock function returns a promise that resolves to a function which,
 // when called, allows the next call to lock to proceed.
 // https://stackoverflow.com/a/51086893
@@ -95,8 +120,9 @@ function Mutex() {
     }
 }
 
-// Enforces exclusive access to onBeforeSendHeaders listeners.
+// Enforce exclusive access to onBeforeSendHeaders and onRequest listeners.
 const headersMutex = new Mutex();
+const proxyMutex = new Mutex();
 
 async function roundtrip(request) {
     // Process the incoming request spec and convert it into parameters to the
@@ -132,8 +158,6 @@ async function roundtrip(request) {
     // Don't follow redirects (we'll get resp.status:0 if there is one).
     init.redirect = "manual";
 
-    // TODO: proxy
-
     // We need to use a webRequest.onBeforeSendHeaders listener to override
     // certain header fields, including Host (passing them to fetch in
     // init.headers does not work). But onBeforeSendHeaders is a global setting
@@ -173,6 +197,27 @@ async function roundtrip(request) {
         }
     }
 
+    // Similarly, for controlling the proxy for each request, we set a
+    // proxy.onRequest listener, use it for one request, then remove it.
+    let proxyUnlock = await proxyMutex.lock();
+    let proxyCalled = false;
+    // async to make exceptions visible to proxy.onError.
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=1528873#c1
+    async function proxyFn(details) {
+        try {
+            // Sanity assertion: per-request listeners are called at most once.
+            if (proxyCalled) {
+                throw new Error("proxyFn called more than once");
+            }
+            proxyCalled = true;
+
+            return makeProxyInfo(request.proxy);
+        } finally {
+            browser.proxy.onRequest.removeListener(proxyFn);
+            proxyUnlock();
+        }
+    }
+
     try {
         // Set our listener that overrides the headers for this request.
         // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/onBeforeSendHeaders
@@ -181,18 +226,27 @@ async function roundtrip(request) {
             {urls: ["http://*/*", "https://*/*"]},
             ["blocking", "requestHeaders"]
         );
+        // Set our listener that overrides the proxy for this request.
+        // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/onRequest
+        browser.proxy.onRequest.addListener(
+            proxyFn,
+            {urls: ["http://*/*", "https://*/*"]}
+        );
 
         // Now actually do the request and build a response object.
         let resp = await fetch(url, init);
         let body = await resp.arrayBuffer();
         return {status: resp.status, body: base64_encode(body)};
     } finally {
-        // With certain errors (e.g. an invalid URL), the onBeforeSendHeaders
-        // listener may never get called, and therefore never release its lock.
-        // Ensure that locks are released and listeners removed in any case.
-        // It's safe to release a lock or remove a listener more than once.
+        // With certain errors (e.g. an invalid URL), our onBeforeSendHeaders
+        // and onRequest listeners may never get called, and therefore never
+        // release their locks. Ensure that locks are released and listeners
+        // removed in any case. It's safe to release a lock or remove a listener
+        // more than once.
         browser.webRequest.onBeforeSendHeaders.removeListener(headersFn);
         headersUnlock();
+        browser.proxy.onRequest.removeListener(proxyFn);
+        proxyUnlock();
     }
 }
 
diff --git a/webextension/manifest.json b/webextension/manifest.json
index a079833..fc56723 100644
--- a/webextension/manifest.json
+++ b/webextension/manifest.json
@@ -16,6 +16,7 @@
 
 	"permissions": [
 		"nativeMessaging",
+		"proxy",
 		"webRequest",
 		"webRequestBlocking",
 		"https://*/*",
diff --git a/webextension/native/main.go b/webextension/native/main.go
index 3b71da7..e98c4f1 100644
--- a/webextension/native/main.go
+++ b/webextension/native/main.go
@@ -68,7 +68,7 @@ type requestSpec struct {
 type proxySpec struct {
 	Type string `json:"type"`
 	Host string `json:"host"`
-	Port string `json:"port"`
+	Port int    `json:"port"`
 }
 
 // A specification of an HTTP request or an error, as sent via the socket to





More information about the tor-commits mailing list