Browse Source

fix: pass full response headers in net module (#21552)

Cheng Zhao 5 years ago
parent
commit
ceacadb4f4
3 changed files with 63 additions and 13 deletions
  1. 15 6
      lib/browser/api/net.js
  2. 31 7
      shell/browser/api/atom_api_url_loader.cc
  3. 17 0
      spec-main/api-net-spec.ts

+ 15 - 6
lib/browser/api/net.js

@@ -53,18 +53,27 @@ class IncomingMessage extends Readable {
 
   get headers () {
     const filteredHeaders = {}
-    const rawHeaders = this._responseHead.headers
-    Object.keys(rawHeaders).forEach(header => {
-      if (header in filteredHeaders && discardableDuplicateHeaders.has(header)) {
+    const { rawHeaders } = this._responseHead
+    rawHeaders.forEach(header => {
+      if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key) &&
+          discardableDuplicateHeaders.has(header.key)) {
         // do nothing with discardable duplicate headers
       } else {
-        if (header === 'set-cookie') {
+        if (header.key === 'set-cookie') {
           // keep set-cookie as an array per Node.js rules
           // see https://nodejs.org/api/http.html#http_message_headers
-          filteredHeaders[header] = rawHeaders[header]
+          if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
+            filteredHeaders[header.key].push(header.value)
+          } else {
+            filteredHeaders[header.key] = [header.value]
+          }
         } else {
           // for non-cookie headers, the values are joined together with ', '
-          filteredHeaders[header] = rawHeaders[header].join(', ')
+          if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
+            filteredHeaders[header.key] += `, ${header.value}`
+          } else {
+            filteredHeaders[header.key] = header.value
+          }
         }
       }
     })

+ 31 - 7
shell/browser/api/atom_api_url_loader.cc

@@ -29,6 +29,28 @@
 #include "shell/common/gin_helper/object_template_builder.h"
 #include "shell/common/node_includes.h"
 
+namespace gin {
+
+template <>
+struct Converter<network::mojom::HttpRawHeaderPairPtr> {
+  static v8::Local<v8::Value> ToV8(
+      v8::Isolate* isolate,
+      const network::mojom::HttpRawHeaderPairPtr& pair) {
+    gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
+    dict.Set("key", base::ToLowerASCII(pair->key));
+    dict.Set("value", pair->value);
+    return dict.GetHandle();
+  }
+};
+
+}  // namespace gin
+
+namespace electron {
+
+namespace api {
+
+namespace {
+
 class BufferDataSource : public mojo::DataPipeProducer::DataSource {
  public:
   explicit BufferDataSource(base::span<char> buffer) {
@@ -198,12 +220,6 @@ class JSChunkedDataPipeGetter : public gin::Wrappable<JSChunkedDataPipeGetter>,
 gin::WrapperInfo JSChunkedDataPipeGetter::kWrapperInfo = {
     gin::kEmbedderNativeGin};
 
-namespace electron {
-
-namespace api {
-
-namespace {
-
 const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
     net::DefineNetworkTrafficAnnotation("electron_net_module", R"(
         semantics {
@@ -341,6 +357,10 @@ gin_helper::WrappableBase* SimpleURLLoaderWrapper::New(gin::Arguments* args) {
     }
   }
 
+  // Chromium filters headers using browser rules, while for net module we have
+  // every header passed.
+  request->report_raw_headers = true;
+
   v8::Local<v8::Value> body;
   v8::Local<v8::Value> chunk_pipe_getter;
   if (opts.Get("body", &body)) {
@@ -415,8 +435,12 @@ void SimpleURLLoaderWrapper::OnResponseStarted(
   gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate());
   dict.Set("statusCode", response_head.headers->response_code());
   dict.Set("statusMessage", response_head.headers->GetStatusText());
-  dict.Set("headers", response_head.headers.get());
   dict.Set("httpVersion", response_head.headers->GetHttpVersion());
+  // Note that |response_head.headers| are filtered by Chromium and should not
+  // be used here.
+  DCHECK(response_head.raw_request_response_info);
+  dict.Set("rawHeaders",
+           response_head.raw_request_response_info->response_headers);
   Emit("response-started", final_url, dict);
 }
 

+ 17 - 0
spec-main/api-net-spec.ts

@@ -616,6 +616,23 @@ describe('net module', () => {
       })
     })
 
+    it('should be able to receive cookies', (done) => {
+      const cookie = ['cookie1', 'cookie2']
+      respondOnce.toSingleURL((request, response) => {
+        response.statusCode = 200
+        response.statusMessage = 'OK'
+        response.setHeader('set-cookie', cookie)
+        response.end()
+      }).then(serverUrl => {
+        const urlRequest = net.request(serverUrl)
+        urlRequest.on('response', (response) => {
+          expect(response.headers['set-cookie']).to.have.same.members(cookie)
+          done()
+        })
+        urlRequest.end()
+      })
+    })
+
     it('should be able to abort an HTTP request before first write', async () => {
       const serverUrl = await respondOnce.toSingleURL((request, response) => {
         response.end()