Repository: fastify/fastify (JavaScript)

Issue:
Response cookies are not being parsed correctly on browser.
## 🐛 Bug Report

In my case, the browser is not able to parse response cookies correctly.  When I set multiple cookies, following line of 'Reply.header' function is combining the cookies into one header ('set-cookie') with comma separated cookies. 

```
this._headers[_key] = [this._headers[_key]].concat(value)
````
Chrome browser is not able to parse all cookies except the first one. 

## To Reproduce

I am using fastify-http-proxy and in beforeHandler, trying to set multiple cookies on reply like 

```
reply.setCookie("cookie1", "", {httpOnly: true})
reply.setCookie("cookie2", "", {httpOnly: true})
reply.setCookie("cookie3", "", {httpOnly: true})
reply.setCookie("cookie4", "", {httpOnly: true})

```

## Expected behavior

Not sure if it's a bug but it seems we should have multiple header entries for each cookie if we are calling Reply.header multiple times for setting the cookies.

More details are given in this link.
https://stackoverflow.com/questions/11533867/set-cookie-header-with-multiple-cookies

This may fix the issue.

```
this._headers[_key] = this._headers[_key].concat(value)
````

## Your Environment

- *node version*: 8.9.1
- *fastify version*: >=1.13.0
- *os*: Mac
- *browser*: Chrome: 71.0.3578.98

cc @jsumners  as he submitted the PR and made changes to this area.
https://github.com/fastify/fastify/issues/834



Discussion:
Would you like to send a PR to address this issue? It seems you have investigated it quite well. If not, can you please include an example to replicate this issue on Node.js only? It would be helpful to create a unit test.
https://tools.ietf.org/html/rfc2109#section-4.2.2
> Informally, the Set-Cookie response header comprises the token Set-
   Cookie:, followed by a comma-separated list of one or more cookies.

RFC 2965 supersedes RFC 2109. It says:

https://tools.ietf.org/html/rfc2965
> Informally, the Set-Cookie2 response header comprises the token Set-
   Cookie2:, followed by a comma-separated list of one or more cookies.

In turn, RFC 6265 supersedes RFC 2109. However, it does not make the same statement in https://tools.ietf.org/html/rfc6265#section-4.1.1 . Instead, it says:

> Informally, the Set-Cookie response header contains the header name
   "Set-Cookie" followed by a ":" and a cookie.  Each cookie begins with
   a name-value-pair, followed by zero or more attribute-value pairs.

I believe the intention there is to state that a `Set-Cookie` header contains only one cookie, but  the "Each cookie begins with a name-value-pair..." clause leaves it open to interpretation. In such case, I think it is fair to draw on the long established format of the previous RFCs. Which is to say, I do not think the implementation in Fastify is incorrect.

Regardless, a PR to make it work with browsers ignoring syntax that has been valid for literal decades would be welcome.
Thanks, @mcollina, @jsumners. I have created a PR for your review. 



Patch:
diff --git a/lib/reply.js b/lib/reply.js
index 9d87964e61e..612ef9a23ce 100644
--- a/lib/reply.js
+++ b/lib/reply.js
@@ -159,7 +159,14 @@ Reply.prototype.header = function (key, value) {
 
   if (this[kReplyHeaders][_key] && _key === 'set-cookie') {
     // https://tools.ietf.org/html/rfc7230#section-3.2.2
-    this[kReplyHeaders][_key] = [this[kReplyHeaders][_key]].concat(value)
+    if (typeof this[kReplyHeaders][_key] === 'string') {
+      this[kReplyHeaders][_key] = [this[kReplyHeaders][_key]]
+    }
+    if (Array.isArray(value)) {
+      Array.prototype.push.apply(this[kReplyHeaders][_key], value)
+    } else {
+      this[kReplyHeaders][_key].push(value)
+    }
   } else {
     this[kReplyHeaders][_key] = value
   }


Test patch:
diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js
index 0178b7ca44c..8f6295c05c4 100644
--- a/test/internals/reply.test.js
+++ b/test/internals/reply.test.js
@@ -903,3 +903,38 @@ test('.status() is an alias for .code()', t => {
     t.is(res.statusCode, 418)
   })
 })
+
+test('reply.header setting multiple cookies as multiple Set-Cookie headers', t => {
+  t.plan(7)
+
+  const fastify = require('../../')()
+
+  fastify.get('/headers', function (req, reply) {
+    reply
+      .header('set-cookie', 'one')
+      .header('set-cookie', 'two')
+      .header('set-cookie', 'three')
+      .header('set-cookie', ['four', 'five', 'six'])
+      .send({})
+  })
+
+  fastify.listen(0, err => {
+    t.error(err)
+    fastify.server.unref()
+
+    sget({
+      method: 'GET',
+      url: 'http://localhost:' + fastify.server.address().port + '/headers'
+    }, (err, response, body) => {
+      t.error(err)
+      t.ok(response.headers['set-cookie'])
+      t.strictDeepEqual(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six'])
+    })
+  })
+
+  fastify.inject('/headers', (error, response) => {
+    t.error(error)
+    t.ok(response.headers['set-cookie'])
+    t.strictDeepEqual(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six'])
+  })
+})