Repository: ogen-go/ogen
Pull request #1027 (2023-09-06 00:50:45)

fix: do not fold Set-Cookie values into a single header, per RFC6265
This PR adds a special case for encoding `Set-Cookie` headers in responses. Specifically, it prevents `headerParamEncoder` from folding multiple header values into a single header value using a comma separator, as per [RFC6265](https://datatracker.ietf.org/doc/html/rfc6265):

>   Origin servers SHOULD NOT fold multiple `Set-Cookie` header fields into a single header field.  The usual mechanism for folding HTTP headers fields (i.e., as defined in [RFC2616 section 4.2](https://www.rfc-editor.org/rfc/rfc2616#section-4.2)) might change the semantics of the `Set-Cookie` header field because the `%x2C` (`,`) character is used by `Set-Cookie` in a way that conflicts with such folding.

The comma mentioned by the RFC appears when using a cookie with an expiry timestamp, because the recommended timestamp format (see [RFC2616 section 3.3.1](https://www.rfc-editor.org/rfc/rfc2616#section-3.3.1)) uses a comma after the day-of-week.

/cc @ezzatron

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/ogen-go/ogen/pull/1027?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go) Report
Patch coverage: **`83.33%`** and project coverage change: **`+0.02%`** :tada:
> Comparison is base [(`660d20e`)](https://app.codecov.io/gh/ogen-go/ogen/commit/660d20eb8dcf53f01908894569fc602dce585d60?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go) 72.20% compared to head [(`5e4442d`)](https://app.codecov.io/gh/ogen-go/ogen/pull/1027?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go) 72.23%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1027      +/-   ##
==========================================
+ Coverage   72.20%   72.23%   +0.02%     
==========================================
  Files         192      192              
  Lines       14991    15003      +12     
==========================================
+ Hits        10825    10837      +12     
  Misses       3631     3631              
  Partials      535      535              
```


| [Files Changed](https://app.codecov.io/gh/ogen-go/ogen/pull/1027?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go) | Coverage Δ | |
|---|---|---|
| [uri/header\_param\_encoder.go](https://app.codecov.io/gh/ogen-go/ogen/pull/1027?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go#diff-dXJpL2hlYWRlcl9wYXJhbV9lbmNvZGVyLmdv) | `69.76% <83.33%> (+11.70%)` | :arrow_up: |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/ogen-go/ogen/pull/1027?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go).


Patch:
diff --git a/uri/header_param_encoder.go b/uri/header_param_encoder.go
index 0e22c7db..205e7496 100644
--- a/uri/header_param_encoder.go
+++ b/uri/header_param_encoder.go
@@ -20,13 +20,26 @@ func (e *headerParamEncoder) serialize() error {
 		e.header.Set(e.paramName, e.val)
 		return nil
 	case typeArray:
-		const sep = ","
-		for _, val := range e.items {
-			if err := checkNotContains(val, sep); err != nil {
-				return err
+		if strings.EqualFold(e.paramName, "set-cookie") {
+			// As per RFC6265:
+			//
+			// Origin servers SHOULD NOT fold multiple Set-Cookie header fields into
+			// a single header field. The usual mechanism for folding HTTP headers
+			// fields (i.e., as defined in RFC2616) might change the semantics of
+			// the Set-Cookie header field because the %x2C (",") character is used
+			// by Set-Cookie in a way that conflicts with such folding.
+			for _, val := range e.items {
+				e.header.Add(e.paramName, val)
+			}
+		} else {
+			const sep = ","
+			for _, val := range e.items {
+				if err := checkNotContains(val, sep); err != nil {
+					return err
+				}
 			}
+			e.header.Set(e.paramName, strings.Join(e.items, sep))
 		}
-		e.header.Set(e.paramName, strings.Join(e.items, sep))
 		return nil
 	case typeObject:
 		var kvSep, fieldSep byte = ',', ','


Test patch:
diff --git a/uri/header_param_encoder_test.go b/uri/header_param_encoder_test.go
index 2d6f9d17..a1d75b19 100644
--- a/uri/header_param_encoder_test.go
+++ b/uri/header_param_encoder_test.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"net/http"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/require"
 )
@@ -70,6 +71,27 @@ func TestHeaderParamEncoder(t *testing.T) {
 					"X-Myheader": []string{"3,4,5"},
 				},
 			},
+			{
+				HeaderName: "Set-Cookie",
+				Input: []string{
+					(&http.Cookie{
+						Name:  "key1",
+						Value: "value1",
+					}).String(),
+					(&http.Cookie{
+						Name:    "key2",
+						Value:   "value2",
+						Expires: time.Date(2023, 9, 6, 10, 11, 12, 0, time.UTC),
+					}).String(),
+				},
+				Explode: true,
+				Expect: http.Header{
+					"Set-Cookie": []string{
+						"key1=value1",
+						"key2=value2; Expires=Wed, 06 Sep 2023 10:11:12 GMT",
+					},
+				},
+			},
 		}
 		for i, test := range tests {
 			e := headerParamEncoder{