Repository: chimurai/http-proxy-middleware (ts)

Issue:
responseInterceptor changes headers
### Is this a bug report?
Yes

### Steps to reproduce

I have a (nasty) legacy app that I'm trying to migrate away from, and I'm using `http-proxy-middleware` to wrap endpoints of the new and old backends. Some of the old app serves a horrible mashup of PHP, JS, and HTML, often with db queries embedded directly in the HTML/PHP files. I need to route to these, but I also need to make some changes to the HTML, pending a real overhaul of the backend. The `responseInterceptor` should be the perfect tool for this, but for some reason the response I get never loads: i.e. when I try to log in to our web app (legacy API), rather than serving me the homepage, I get redirected back to the login page. I recognize this may be a problem on my end, _but_ the same process works fine if I use `proxyRes.on("data", (data) => {};` 


### This breaks:
```
router.use("/", proxy({
  target: <my app url>,
  changeOrigin: true,
  ws: true,
  selfHandleResponse: true,
  onProxyRes: responseInterceptor(
    async (responseBuffer, proxyRes, req, res) => {
      return responseBuffer;
    }
  ),
}));
```
This returns headers:
```
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
content-type: text/html; charset=UTF-8
Connection: close
content-length: 120251
Date: Tue, 20 Apr 2021 12:28:42 GMT
```


### This works fine:
```
router.use("/", proxy({
  target: <my app url>,
  changeOrigin: true,
  ws: true,
  onProxyRes: (proxyRes, req, res) => {
    proxyRes.on("data", (data) => {
      const bufferAsString = data.toString("utf-8");
      console.log(bufferAsString);
    });
  },
}));
```

Which returns the desired headers:
```
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
server: nginx/1.18.0 (Ubuntu)
date: Tue, 20 Apr 2021 12:18:51 GMT
content-type: image/svg+xml
content-length: 2188
connection: close
last-modified: Thu, 28 Jan 2021 09:44:48 GMT
etag: "60128790-88c"
expires: Tue, 20 Apr 2021 13:18:51 GMT
cache-control: max-age=3600, public
accept-ranges: bytes
```

### Expected behavior

I would expect `responseInterceptor`, when used in such a way that it does not modify the response buffer, to be side-effect free.

### Actual behavior

Instead, it breaks my app :frowning_face: 

### Setup

- http-proxy-middleware: _1.1.2_
- http-proxy-middleware configuration: vanilla; see snippets above
- server: express _4.17.1_

#### client info

Firefox/Chrome


Pull request:
fix(response interceptor): proxy original response headers
fixes #562 

Patch:
diff --git a/src/handlers/response-interceptor.ts b/src/handlers/response-interceptor.ts
index da94aee..cb3c967 100644
--- a/src/handlers/response-interceptor.ts
+++ b/src/handlers/response-interceptor.ts
@@ -31,8 +31,8 @@ export function responseInterceptor(interceptor: Interceptor) {
     _proxyRes.on('data', (chunk) => (buffer = Buffer.concat([buffer, chunk])));
 
     _proxyRes.on('end', async () => {
-      // set original content type from upstream
-      res.setHeader('content-type', originalProxyRes.headers['content-type'] || '');
+      // copy original headers
+      copyHeaders(proxyRes, res);
 
       // call interceptor with intercepted response (buffer)
       const interceptedBuffer = Buffer.from(await interceptor(buffer, originalProxyRes, req, res));
@@ -79,3 +79,33 @@ function decompress(proxyRes: http.IncomingMessage, contentEncoding: string) {
 
   return _proxyRes;
 }
+
+/**
+ * Copy original headers
+ * https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L78
+ */
+function copyHeaders(originalResponse, response) {
+  response.statusCode = originalResponse.statusCode;
+  response.statusMessage = originalResponse.statusMessage;
+
+  if (response.setHeader) {
+    let keys = Object.keys(originalResponse.headers);
+
+    // ignore chunked, brotli, gzip, deflate headers
+    keys = keys.filter((key) => !['content-encoding', 'transfer-encoding'].includes(key));
+
+    keys.forEach((key) => {
+      let value = originalResponse.headers[key];
+
+      if (key === 'set-cookie') {
+        // remove cookie domain
+        value = Array.isArray(value) ? value : [value];
+        value = value.map((x) => x.replace(/Domain=[^;]+?/i, ''));
+      }
+
+      response.setHeader(key, value);
+    });
+  } else {
+    response.headers = originalResponse.headers;
+  }
+}


Test patch:
diff --git a/test/e2e/response-interceptor.spec.ts b/test/e2e/response-interceptor.spec.ts
index d6be785..1cfaf0a 100644
--- a/test/e2e/response-interceptor.spec.ts
+++ b/test/e2e/response-interceptor.spec.ts
@@ -41,6 +41,32 @@ describe('responseInterceptor()', () => {
     });
   });
 
+  describe('intercept responses with original headers', () => {
+    beforeEach(() => {
+      agent = request(
+        createApp(
+          createProxyMiddleware({
+            target: `http://httpbin.org`,
+            changeOrigin: true, // for vhosted sites, changes host header to match to target's host
+            selfHandleResponse: true,
+            onProxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
+              return responseBuffer;
+            }),
+          })
+        )
+      );
+    });
+
+    it('should proxy and return original headers from http://httpbin.org/cookies/set/cookie/monster', async () => {
+      return agent
+        .get(`/cookies/set/cookie/monster`)
+        .expect('Access-Control-Allow-Origin', '*')
+        .expect('Date', /.+/)
+        .expect('set-cookie', /.*cookie=monster.*/)
+        .expect(302);
+    });
+  });
+
   describe('intercept compressed responses', () => {
     beforeEach(() => {
       agent = request(