<p dir="auto">I'm having an issue that has me questioning Requests's (Oreo's?) cookie handling.  Using Requests, I submit a request that successfully authenticates me to a particular server. The response includes a cookie whose <em>value</em> contains an equals ("=") character.  Because the cookie's value contains "=", which is not a "legal" char (as per _LegalChars in monkeys.py), Requests quotes the entire cookie value when it submits it in subsequent requests.  The server, though, considers the quoted cookie to be different than the cookie it sent to the client -- so the client is no longer authenticated.</p>
<p dir="auto">See this flow:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="In [6]: session = requests.session(headers=_default_headers)

In [7]: data = {
   ...:     'userID': 'myuser',
   ...:     'password': 'mypassword',
   ...: }

In [8]: resp = session.post('https://www.discovercard.com/cardmembersvcs/loginlogout/app/signin', data=data)

In [9]: resp.headers
Out[9]: 
{'cache-control': 'no-cache=&quot;set-cookie, set-cookie2&quot;',
 'connection': 'Keep-Alive',
&lt;snip&gt;
 'set-cookie': 'v1st=6B2AF394DCD43A57; path=/; expires=Wed, 19 Feb 2020 14:28:00 GMT; domain=.discovercard.com,
 dfsedskey=62689366; Expires=Sun, 17 Feb 2013 17:25:06 GMT; Path=/; Domain=.discovercard.com,
 PMData=PMV3EKKwYWpRB0ZAh7NnXgngf+fGUH7/c6RfKfa7Hiv5v2h1S1iS4MjexZtrnrPMErcNR0eyCzoWUfNFrj6bUkqxR0aQ==; Expires=Sun, 17 Feb 2013 17:25:06 GMT; Path=/; Domain=.discovercard.com; Secure,
 dcsession=E7dc4gmtmOk9LkTB4xeTjRs82g8=; Path=/; Domain=.discovercard.com; Secure,
 ACLOGIN=incntvtyp1=CBB; Expires=Sun, 17 Feb 2013 17:25:06 GMT; Path=/; Domain=.discovercard.com,
 ACHASH=1338528939; Expires=Sun, 17 Feb 2013 17:25:06 GMT; Path=/; Domain=.discovercard.com, dcuserid=***CLEARED***; Path=/; Domain=.discovercard.com,
 JSESSIONID=0001cT7y8RWiiQDuizJpy2gjQWw:16cfscb3m; Path=/',
&lt;snip&gt;
}"><pre class="notranslate"><code class="notranslate">In [6]: session = requests.session(headers=_default_headers)

In [7]: data = {
   ...:     'userID': 'myuser',
   ...:     'password': 'mypassword',
   ...: }

In [8]: resp = session.post('https://www.discovercard.com/cardmembersvcs/loginlogout/app/signin', data=data)

In [9]: resp.headers
Out[9]: 
{'cache-control': 'no-cache="set-cookie, set-cookie2"',
 'connection': 'Keep-Alive',
&lt;snip&gt;
 'set-cookie': 'v1st=6B2AF394DCD43A57; path=/; expires=Wed, 19 Feb 2020 14:28:00 GMT; domain=.discovercard.com,
 dfsedskey=62689366; Expires=Sun, 17 Feb 2013 17:25:06 GMT; Path=/; Domain=.discovercard.com,
 PMData=PMV3EKKwYWpRB0ZAh7NnXgngf+fGUH7/c6RfKfa7Hiv5v2h1S1iS4MjexZtrnrPMErcNR0eyCzoWUfNFrj6bUkqxR0aQ==; Expires=Sun, 17 Feb 2013 17:25:06 GMT; Path=/; Domain=.discovercard.com; Secure,
 dcsession=E7dc4gmtmOk9LkTB4xeTjRs82g8=; Path=/; Domain=.discovercard.com; Secure,
 ACLOGIN=incntvtyp1=CBB; Expires=Sun, 17 Feb 2013 17:25:06 GMT; Path=/; Domain=.discovercard.com,
 ACHASH=1338528939; Expires=Sun, 17 Feb 2013 17:25:06 GMT; Path=/; Domain=.discovercard.com, dcuserid=***CLEARED***; Path=/; Domain=.discovercard.com,
 JSESSIONID=0001cT7y8RWiiQDuizJpy2gjQWw:16cfscb3m; Path=/',
&lt;snip&gt;
}
</code></pre></div>
<p dir="auto">So the <em>value</em> of the cookie with key "dcsession" is "E7dc4gmtmOk9LkTB4xeTjRs82g8=".  The cookies dict is as expected:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="In [10]: session.cookies['dcsession']
Out[10]: 'E7dc4gmtmOk9LkTB4xeTjRs82g8='

In [11]: resp.cookies['dcsession']
Out[11]: 'E7dc4gmtmOk9LkTB4xeTjRs82g8='"><pre class="notranslate"><code class="notranslate">In [10]: session.cookies['dcsession']
Out[10]: 'E7dc4gmtmOk9LkTB4xeTjRs82g8='

In [11]: resp.cookies['dcsession']
Out[11]: 'E7dc4gmtmOk9LkTB4xeTjRs82g8='
</code></pre></div>
<p dir="auto">But when I make my next request to the server:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="In [12]: resp = session.get('https://www.discovercard.com/cardmembersvcs/achome/homepage')"><pre class="notranslate"><code class="notranslate">In [12]: resp = session.get('https://www.discovercard.com/cardmembersvcs/achome/homepage')
</code></pre></div>
<p dir="auto">The response to that request indicates "session timed out or not established".  This is because of how the cookie was sent in this request:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="In [14]: resp.request.headers['Cookie']
Out[14]: 'ACHASH=1338528939;  ACLOGIN=&quot;incntvtyp1=CBB&quot;;  JSESSIONID=&quot;0001cT7y8RWiiQDuizJpy2gjQWw:16cfscb3m&quot;;
PMData=&quot;PMV3EKKwYWpRB0ZAh7NnXgngf+fGUH7/c6RfKfa7Hiv5v2h1S1iS4MjexZtrnrPMErcNR0eyCzoWUfNFrj6bUkqxR0aQ==&quot;;
dcsession=&quot;E7dc4gmtmOk9LkTB4xeTjRs82g8=&quot;;  dcuserid=***CLEARED***;  dfsedskey=62689366;  v1st=6B2AF394DCD43A57'"><pre class="notranslate"><code class="notranslate">In [14]: resp.request.headers['Cookie']
Out[14]: 'ACHASH=1338528939;  ACLOGIN="incntvtyp1=CBB";  JSESSIONID="0001cT7y8RWiiQDuizJpy2gjQWw:16cfscb3m";
PMData="PMV3EKKwYWpRB0ZAh7NnXgngf+fGUH7/c6RfKfa7Hiv5v2h1S1iS4MjexZtrnrPMErcNR0eyCzoWUfNFrj6bUkqxR0aQ==";
dcsession="E7dc4gmtmOk9LkTB4xeTjRs82g8=";  dcuserid=***CLEARED***;  dfsedskey=62689366;  v1st=6B2AF394DCD43A57'
</code></pre></div>
<p dir="auto">Because the server considers the quoted cookie being returned as being different from the non-quoted cookie it sent, the session is no longer authenticated.</p>
<p dir="auto">The condition where the cookie comes back from the server containing the equals character is not rare or intermittent -- it always comes back like this, and so the authentication always fails.  This is not broken for people using browsers!  No browser I've tried (Chrome, Safari) quotes the cookie values, so there's no problem authenticating and carrying on a session.  Obviously the server in this case is an established financial institution, presumably with many millions of users, so I think that the way Requests/Oreos is handling cookies is a problem.</p>
<p dir="auto">From what I can tell from the code, Requests instantiates a monkeys.SimpleCookie.  SimpleCookie decides that, since the value contains something other than what's in _LegalChars, it needs to quote the value.  I can patch this so that it works for me any number of ways: adding "=" into _LegalChars, or preventing SimpleCookie from ever quoting a value, or having Requests use BaseCookie rather than SimpleCookie, or having my application re-construct the cookie header from the cookies before my next request, etc.  But this does feel like a bug that should be fixed in the main branch for everybody.  I'm happy to contribute a fix but am interested to know what Kenneth and/or anybody else thinks is the right way to do it.</p>
<p dir="auto">test code:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="from requests import session

with session() as c:
    c.get('http://www.baidu.com')
    #should have cookie headers
    c.get('http://www.baidu.com')"><pre class="notranslate"><span class="pl-k">from</span> <span class="pl-s1">requests</span> <span class="pl-k">import</span> <span class="pl-s1">session</span>

<span class="pl-k">with</span> <span class="pl-en">session</span>() <span class="pl-k">as</span> <span class="pl-s1">c</span>:
    <span class="pl-s1">c</span>.<span class="pl-en">get</span>(<span class="pl-s">'http://www.baidu.com'</span>)
    <span class="pl-c">#should have cookie headers</span>
    <span class="pl-s1">c</span>.<span class="pl-en">get</span>(<span class="pl-s">'http://www.baidu.com'</span>)</pre></div>
<p dir="auto">Server:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="Set-Cookie: BAIDUID=DC1510722CF5A19A2A4C4E4DB720C5D4:FG=1; expires=Wed, 23-Nov-41 17:44:00 GMT; path=/; domain=.baidu.com"><pre class="notranslate"><span class="pl-v">Set</span><span class="pl-c1">-</span><span class="pl-v">Cookie</span>: <span class="pl-v">BAIDUID</span><span class="pl-c1">=</span><span class="pl-v">DC1510722CF5A19A2A4C4E4DB720C5D4</span>:<span class="pl-v">FG</span><span class="pl-c1">=</span><span class="pl-c1">1</span>; <span class="pl-s1">expires</span><span class="pl-c1">=</span><span class="pl-v">Wed</span>, <span class="pl-c1">23</span><span class="pl-c1">-</span><span class="pl-v">Nov</span><span class="pl-c1">-</span><span class="pl-c1">41</span> <span class="pl-c1">17</span>:<span class="pl-c1">44</span>:<span class="pl-c1">00</span> <span class="pl-v">GMT</span>; <span class="pl-s1">path</span><span class="pl-c1">=</span><span class="pl-c1">/</span>; <span class="pl-s1">domain</span><span class="pl-c1">=</span>.<span class="pl-s1">baidu</span>.<span class="pl-s1">com</span></pre></div>
<p dir="auto">Client:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="Cookie: BAIDUID=&quot;DC1510722CF5A19A2A4C4E4DB720C5D4:FG=1&quot;"><pre class="notranslate"><span class="pl-v">Cookie</span>: <span class="pl-v">BAIDUID</span><span class="pl-c1">=</span><span class="pl-s">"DC1510722CF5A19A2A4C4E4DB720C5D4:FG=1"</span></pre></div>
<p dir="auto">The correct result should be:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="Cookie: BAIDUID=DC1510722CF5A19A2A4C4E4DB720C5D4:FG=1"><pre class="notranslate"><span class="pl-v">Cookie</span>: <span class="pl-v">BAIDUID</span><span class="pl-c1">=</span><span class="pl-v">DC1510722CF5A19A2A4C4E4DB720C5D4</span>:<span class="pl-v">FG</span><span class="pl-c1">=</span><span class="pl-c1">1</span></pre></div>
<p dir="auto">quote function</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="def _quote(str, LegalChars=_LegalChars,
           idmap=_idmap, translate=string.translate):
    #
    # If the string does not need to be double-quoted,
    # then just return the string.  Otherwise, surround
    # the string in doublequotes and precede quote (with a \)
    # special characters.
    #
    if &quot;&quot; == translate(str, idmap, LegalChars):
        return str
    else:
        return '&quot;' + _nulljoin( map(_Translator.get, str, str) ) + '&quot;'
# end _quote"><pre class="notranslate"><span class="pl-k">def</span> <span class="pl-en">_quote</span>(<span class="pl-s1">str</span>, <span class="pl-v">LegalChars</span><span class="pl-c1">=</span><span class="pl-s1">_LegalChars</span>,
           <span class="pl-s1">idmap</span><span class="pl-c1">=</span><span class="pl-s1">_idmap</span>, <span class="pl-s1">translate</span><span class="pl-c1">=</span><span class="pl-s1">string</span>.<span class="pl-s1">translate</span>):
    <span class="pl-c">#</span>
    <span class="pl-c"># If the string does not need to be double-quoted,</span>
    <span class="pl-c"># then just return the string.  Otherwise, surround</span>
    <span class="pl-c"># the string in doublequotes and precede quote (with a \)</span>
    <span class="pl-c"># special characters.</span>
    <span class="pl-c">#</span>
    <span class="pl-k">if</span> <span class="pl-s">""</span> <span class="pl-c1">==</span> <span class="pl-en">translate</span>(<span class="pl-s1">str</span>, <span class="pl-s1">idmap</span>, <span class="pl-v">LegalChars</span>):
        <span class="pl-k">return</span> <span class="pl-s1">str</span>
    <span class="pl-k">else</span>:
        <span class="pl-k">return</span> <span class="pl-s">'"'</span> <span class="pl-c1">+</span> <span class="pl-en">_nulljoin</span>( <span class="pl-en">map</span>(<span class="pl-s1">_Translator</span>.<span class="pl-s1">get</span>, <span class="pl-s1">str</span>, <span class="pl-s1">str</span>) ) <span class="pl-c1">+</span> <span class="pl-s">'"'</span>
<span class="pl-c"># end _quote</span></pre></div>