<p dir="auto">When Python Requests is used to contact a server which returns multiple <code class="notranslate">Set-Cookie</code> response headers, Python Requests combines them into one header field with the values separated by commas.</p>
<h2 dir="auto">Expected Result</h2>
<p dir="auto">Using Nginx as my origin server with the following config:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        add_header Set-Cookie cookieone=test;
        add_header Set-Cookie cookietwo=testing;
    }"><pre class="notranslate"><code class="notranslate">    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        add_header Set-Cookie cookieone=test;
        add_header Set-Cookie cookietwo=testing;
    }
</code></pre></div>
<p dir="auto">Contacting this origin server I expect to receive two separate <code class="notranslate">Set-Cookie</code> headers with their corresponding values. As per <a href="https://tools.ietf.org/html/rfc6265#section-3" rel="nofollow">RFC6265</a>:</p>
<blockquote>
<p dir="auto">Origin servers SHOULD NOT fold multiple Set-Cookie header fields into<br>
a single header field. The usual mechanism for folding HTTP headers<br>
fields (i.e., as defined in [RFC2616]) might change the semantics of<br>
the Set-Cookie header field because the %x2C (",") character is used<br>
by Set-Cookie in a way that conflicts with such folding.</p>
</blockquote>
<p dir="auto">When using the browser my response from this origin has multiple <code class="notranslate">Set-Cookie</code> headers:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/1361529/36676430-aae682cc-1ad9-11e8-882e-ada65918633f.png"><img src="https://user-images.githubusercontent.com/1361529/36676430-aae682cc-1ad9-11e8-882e-ada65918633f.png" alt="image" style="max-width: 100%;"></a></p>
<h2 dir="auto">Actual Result</h2>
<p dir="auto">Python Requests folds the multiple <code class="notranslate">Set-Cookie</code> headers into a single header field separated by comma(s). For example:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="&gt;&gt;&gt; import requests
&gt;&gt;&gt; r = requests.get('http://localhost')
&gt;&gt;&gt; r
&lt;Response [200]&gt;
&gt;&gt;&gt; r.headers
{'Content-Length': '612', 'Set-Cookie': 'cookieone=test, cookietwo=testing', 'Accept-Ranges': 'bytes', 'Server': 'nginx/1.13.9', 'Last-Modified': 'Tue, 20 Feb 2018 12:21:20 GMT', 'Connection': 'keep-alive', 'ETag': '&quot;5a8c12c0-264&quot;', 'Date': 'Mon, 26 Feb 2018 14:21:59 GMT', 'Content-Type': 'text/html'}
&gt;&gt;&gt; r.cookies
&lt;RequestsCookieJar[Cookie(version=0, name='cookieone', value='test', port=None, port_specified=False, domain='localhost.local', domain_specified=False, domain_initial_dot=False, path='/', path_specified=False, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False), Cookie(version=0, name='cookietwo', value='testing', port=None, port_specified=False, domain='localhost.local', domain_specified=False, domain_initial_dot=False, path='/', path_specified=False, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False)]&gt;"><pre class="notranslate"><code class="notranslate">&gt;&gt;&gt; import requests
&gt;&gt;&gt; r = requests.get('http://localhost')
&gt;&gt;&gt; r
&lt;Response [200]&gt;
&gt;&gt;&gt; r.headers
{'Content-Length': '612', 'Set-Cookie': 'cookieone=test, cookietwo=testing', 'Accept-Ranges': 'bytes', 'Server': 'nginx/1.13.9', 'Last-Modified': 'Tue, 20 Feb 2018 12:21:20 GMT', 'Connection': 'keep-alive', 'ETag': '"5a8c12c0-264"', 'Date': 'Mon, 26 Feb 2018 14:21:59 GMT', 'Content-Type': 'text/html'}
&gt;&gt;&gt; r.cookies
&lt;RequestsCookieJar[Cookie(version=0, name='cookieone', value='test', port=None, port_specified=False, domain='localhost.local', domain_specified=False, domain_initial_dot=False, path='/', path_specified=False, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False), Cookie(version=0, name='cookietwo', value='testing', port=None, port_specified=False, domain='localhost.local', domain_specified=False, domain_initial_dot=False, path='/', path_specified=False, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False)]&gt;
</code></pre></div>
<h2 dir="auto">Reproduction Steps</h2>
<ul dir="auto">
<li>Set up Nginx locally (using Docker <code class="notranslate">docker run -itd -p 80:80 nginx</code>)</li>
<li>Use the Nginx location config above with multiple <code class="notranslate">Set-Cookie</code> headers</li>
<li>Call the Nginx url from the browser and notice multiple <code class="notranslate">Set-Cookie</code> headers (screenshot above)</li>
<li>Call the Nginx url from requests and notice a single, folded, <code class="notranslate">Set-Cookie</code> header (code above)</li>
</ul>
<h2 dir="auto">System Information</h2>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="➜ python -m requests.help
{
  &quot;chardet&quot;: {
    &quot;version&quot;: &quot;3.0.4&quot;
  },
  &quot;cryptography&quot;: {
    &quot;version&quot;: &quot;&quot;
  },
  &quot;idna&quot;: {
    &quot;version&quot;: &quot;2.6&quot;
  },
  &quot;implementation&quot;: {
    &quot;name&quot;: &quot;CPython&quot;,
    &quot;version&quot;: &quot;2.7.10&quot;
  },
  &quot;platform&quot;: {
    &quot;release&quot;: &quot;17.4.0&quot;,
    &quot;system&quot;: &quot;Darwin&quot;
  },
  &quot;pyOpenSSL&quot;: {
    &quot;openssl_version&quot;: &quot;&quot;,
    &quot;version&quot;: null
  },
  &quot;requests&quot;: {
    &quot;version&quot;: &quot;2.18.4&quot;
  },
  &quot;system_ssl&quot;: {
    &quot;version&quot;: &quot;20000000&quot;
  },
  &quot;urllib3&quot;: {
    &quot;version&quot;: &quot;1.22&quot;
  },
  &quot;using_pyopenssl&quot;: false
}"><pre class="notranslate"><code class="notranslate">➜ python -m requests.help
{
  "chardet": {
    "version": "3.0.4"
  },
  "cryptography": {
    "version": ""
  },
  "idna": {
    "version": "2.6"
  },
  "implementation": {
    "name": "CPython",
    "version": "2.7.10"
  },
  "platform": {
    "release": "17.4.0",
    "system": "Darwin"
  },
  "pyOpenSSL": {
    "openssl_version": "",
    "version": null
  },
  "requests": {
    "version": "2.18.4"
  },
  "system_ssl": {
    "version": "20000000"
  },
  "urllib3": {
    "version": "1.22"
  },
  "using_pyopenssl": false
}
</code></pre></div>
<p dir="auto">Jupyterhub is an example of where this is a problem in the wild: <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="296420498" data-permission-text="Title is private" data-url="https://github.com/jupyterhub/jupyterhub/issues/1657" data-hovercard-type="issue" data-hovercard-url="/jupyterhub/jupyterhub/issues/1657/hovercard" href="https://github.com/jupyterhub/jupyterhub/issues/1657">jupyterhub/jupyterhub#1657</a></p>
<p dir="auto">Many web-apps send cookie in multiple Set-Cookie headers, one header for one cookie. Requests joins this headers in one big header separate by comma.</p>
<p dir="auto">From same webapp to requests.get</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Set-Cookie: ASP.NET_SessionId=token1; path=/; HttpOnly
Set-Cookie: Cookie1=token2; path=/ecp
Set-Cookie: X-BEResource=WIN-RBFR0BDA7V7.testlab.net~1; path=/ecp/15.0.516.30; HttpOnly
Set-Cookie: X-BackEndCookie=token3; expires=Mon, 03-Apr-2017 12:25:07 GMT; path=/ecp; HttpOnly"><pre class="notranslate"><code class="notranslate">Set-Cookie: ASP.NET_SessionId=token1; path=/; HttpOnly
Set-Cookie: Cookie1=token2; path=/ecp
Set-Cookie: X-BEResource=WIN-RBFR0BDA7V7.testlab.net~1; path=/ecp/15.0.516.30; HttpOnly
Set-Cookie: X-BackEndCookie=token3; expires=Mon, 03-Apr-2017 12:25:07 GMT; path=/ecp; HttpOnly
</code></pre></div>
<p dir="auto">In requests:<br>
resp.headers  this look like</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Set-Cookie: ASP.NET_SessionId=token1; path=/; HttpOnly,    
  Cookie1=token2; path=/ecp, 
  X-BEResource=WIN-RBFR0BDA7V7.testlab.net~1; path=/ecp/15.0.516.30; HttpOnly, 
  X-BackEndCookie=token3; expires=Thu, 06-Apr-2017 08:27:22 GMT; path=/ecp; HttpOnly"><pre class="notranslate"><code class="notranslate">Set-Cookie: ASP.NET_SessionId=token1; path=/; HttpOnly,    
  Cookie1=token2; path=/ecp, 
  X-BEResource=WIN-RBFR0BDA7V7.testlab.net~1; path=/ecp/15.0.516.30; HttpOnly, 
  X-BackEndCookie=token3; expires=Thu, 06-Apr-2017 08:27:22 GMT; path=/ecp; HttpOnly
</code></pre></div>
<p dir="auto">Firefox, IE, Chrome invalid interpreted this cookies because separater is comma but in  field EXPIRES  comma separate day and date.</p>
<p dir="auto">Principal problem in modification traffic. In my opinion then server send multiple headers lib must return multiple headers</p>