cors-anywhere icon indicating copy to clipboard operation
cors-anywhere copied to clipboard

Updated http-proxy dependency to ^1.15.2

Open idmadj opened this issue 5 years ago • 3 comments

Fixes #182

idmadj avatar Jul 08 '19 23:07 idmadj

CORS Anywhere relies on (undocumented) internals of the http-proxy library. Use a specific version instead of a version range to ensure that CORS Anywhere is guaranteed to work.

Look for "1.11.1" at lib/cors-anywhere.js, verify that the logic still makes sense and replace "1.11.1" with the chosen value.

A bunch of unit tests are also failing with this PR (see the results from Travis), they must pass before the PR can be merged.

Rob--W avatar Jul 09 '19 08:07 Rob--W

since cors-anywhere uses an old http-proxy version, it is impossible to use cors-anywhere and a modern http-proxy's "selfHandleResponse" flag to intercept and rewrite the steams if necessary

https://github.com/http-party/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L157

no "selfHandleResponse" in that old version

27b62a2d73a1b7cdda7239ea6de9c140bf78b11d
 lib/cors-anywhere.js | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 92 insertions(+), 3 deletions(-)

diff --git a/lib/cors-anywhere.js b/lib/cors-anywhere.js
index 1a1c372..4c8da25 100644
--- a/lib/cors-anywhere.js
+++ b/lib/cors-anywhere.js
@@ -8,6 +8,7 @@ var net = require('net');
 var url = require('url');
 var regexp_tld = require('./regexp-top-level-domain');
 var getProxyForUrl = require('proxy-from-env').getProxyForUrl;
+const zlib = require('zlib');
 
 var help_text = {};
 function showUsage(help_file, headers, response) {
@@ -44,6 +45,68 @@ function isValidHostName(hostname) {
   );
 }
 
+const { Transform } = require('stream')
+
+class Filter extends Transform {
+
+  constructor(bodyEnc, destURLString) {
+    super({
+      //readableObjectMode: true,
+      //writableObjectMode: true
+    })
+    this.bufArr = [];
+    this.bodyEnc = bodyEnc;
+    this.destURLString = destURLString;
+  }
+
+  _transform(chunk, encoding, next) {
+      //save ref to buffer-array in array of buffer-arrays
+      //concat array of buffer-arrays later to avoid O(n^2) copying
+      this.bufArr.push(chunk);
+      //do not pass on data
+      return next();
+      //commented out, passes chunks in chain to write sink (dont do that)
+      //null is error obj
+      //return next(null, chunk);
+  }
+  _flush(next) {
+    var error;
+    var uncomp;
+    var compBuf = Buffer.concat(this.bufArr);
+    if (this.bodyEnc == 'gzip') {
+      uncomp = zlib.gunzipSync(compBuf);
+    } else if (this.bodyEnc == 'br') {
+      uncomp = zlib.brotliDecompressSync(compBuf);
+    } else if (this.bodyEnc == 'none'){
+      uncomp = compBuf;
+    } else {
+      error = new Error("HTML Transformer must have encoding type");
+    }
+    var stringBody = uncomp.toString('utf8');
+    debugger;
+    //dont match cdn-cgi directly, and use relative URLs, so if some bizzare
+    //debuggin reason this goes through TWO CORS proxies, both can rewrite the
+    //HTML and correctly handoff
+    stringBody = stringBody.replace("background-image:url('/", "background-image:url('/"+this.destURLString);
+    stringBody = stringBody.replace('href="/', 'href="/'+this.destURLString);
+    stringBody = stringBody.replace('src="/', 'src="/'+this.destURLString);
+    stringBody = stringBody.replace('action="/', 'action="/'+this.destURLString);
+    uncomp = Buffer.from(stringBody);
+    if (this.bodyEnc == 'gzip') {
+      compBuf = zlib.gzipSync(uncomp);
+    } else if (this.bodyEnc == 'br') {
+      compBuf = zlib.brotliCompressSync(uncomp);
+    } else if (this.bodyEnc == 'none'){
+      compBuf = uncomp;
+    }
+    //pass buffer to outgoing to client stream
+    next(error,compBuf);
+  }
+
+}
+
+
+
 /**
  * Adds CORS headers to the response headers.
  *
@@ -112,6 +175,22 @@ function proxyRequest(req, res, proxy) {
           return proxyReqOn.call(this, 'response', function(proxyRes) {
             if (onProxyResponse(proxy, proxyReq, proxyRes, req, res)) {
               try {
+                // test if Cloudflare Captcha screen
+                if (proxyRes.headers["cf-chl-bypass"]) {
+// Hook "proxyRes.pipe(res);" in ancient v1.11.1 http-proxy version before
+// "selfHandleResponse" was introduced because cors-anywhere forces using an
+// old http-proxy version
+// https://github.com/http-party/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L157
+                  var oldPipeFunc = proxyRes.pipe;
+                  proxyRes.pipe = function (dest, pipeOpts) {
+                    var enc = this.headers["content-encoding"] || 'none';
+                    var url = new URL(this.req.agent.protocol+"//"+this.req.getHeader("Host"));
+                    url.port = this.req.socket.remotePort;
+                    var transformer = new Filter(enc, url.toString());
+                    var stream = oldPipeFunc.call(this, transformer);
+                    stream.pipe(dest, pipeOpts);
+                  };
+                }
                 listener(proxyRes);
               } catch (err) {
                 // Wrap in try-catch because an error could occur:

I cooked this up to selectively rewrite HTML inside cors-anywhere but it took too long and its more class monkey method patching and it seems very fragile. Performance is poor in TTTB/etc if the HTML is rewritten, but its very rare in my use case (99% of HTTP reqs go through XHR to API endpoint, rarely a cloudflare captcha comes up and it must be manually solved by the user in an iframe and the XHR JSON POST repeated).

bulk88 avatar Oct 05 '20 00:10 bulk88

I have the same issue. yarn resolutions feature helped to solve it.

  "resolutions": {
    "http-proxy": ">=1.15.2"
  },

artyom-88 avatar Apr 11 '22 18:04 artyom-88