Combine cookies from original request and session file

Close #932
Co-authored-by: kbanc <katherine.bancoft@gmail.com>
Co-authored-by: Gabriel Cruz <gabs.oficial98@gmail.com>
This commit is contained in:
Katherine Bancroft 2020-06-18 17:17:33 -04:00 committed by GitHub
parent 93d07cfe57
commit 9500ce136a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 178 additions and 28 deletions

View File

@ -530,7 +530,6 @@ Simple example:
$ http PUT httpbin.org/put name=John email=john@example.org
.. code-block:: http
PUT / HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
@ -1745,6 +1744,60 @@ exchange after it has been created, specify the session name via
# But it is not updated:
$ http --session-read-only=./ro-session.json httpbin.org/headers Custom-Header:new-value
Cookie Storage Behaviour
------------------------
**TL;DR:** Cookie storage priority: Server response > Command line request > Session file
To set a cookie within a Session there are three options:
1. Get a `Set-Cookie` header in a response from a server
.. code-block:: bash
$ http --session=./session.json httpbin.org/cookie/set?foo=bar
2. Set the cookie name and value through the command line as seen in `cookies`_
.. code-block:: bash
$ http --session=./session.json httpbin.org/headers Cookie:foo=bar
3. Manually set cookie parameters in the json file of the session
.. code-block:: json
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.org/doc#sessions",
"httpie": "2.2.0-dev"
},
"auth": {
"password": null,
"type": null,
"username": null
},
"cookies": {
"foo": {
"expires": null,
"path": "/",
"secure": false,
"value": "bar"
}
}
}
Cookies will be set in the session file with the priority specified above. For example, a cookie
set through the command line will overwrite a cookie of the same name stored
in the session file. If the server returns a `Set-Cookie` header with a
cookie of the same name, the returned cookie will overwrite the preexisting cookie.
Expired cookies are never stored. If a cookie in a session file expires, it will be removed before
sending a new request. If the server expires an existing cookie, it will also be removed from the
session file.
Config
======

View File

@ -4,6 +4,8 @@ Persistent, JSON-serialized sessions.
"""
import os
import re
from http.cookies import SimpleCookie
from pathlib import Path
from typing import Iterable, Optional, Union
from urllib.parse import urlsplit
@ -76,7 +78,13 @@ class Session(BaseConfigDict):
continue # Ignore explicitly unset headers
value = value.decode('utf8')
if name == 'User-Agent' and value.startswith('HTTPie/'):
if name.lower() == 'user-agent' and value.startswith('HTTPie/'):
continue
if name.lower() == 'cookie':
for cookie_name, morsel in SimpleCookie(value).items():
self['cookies'][cookie_name] = {'value': morsel.value}
del request_headers[name]
continue
for prefix in SESSION_IGNORED_HEADER_PREFIXES:

View File

@ -35,6 +35,27 @@ class SessionTestBase:
return MockEnvironment(config_dir=self.config_dir)
class CookieTestBase:
def setup_method(self, method):
self.config_dir = mk_config_dir()
orig_session = {
'cookies': {
'cookie1': {
'value': 'foo',
},
'cookie2': {
'value': 'foo',
}
}
}
self.session_path = self.config_dir / 'test-session.json'
self.session_path.write_text(json.dumps(orig_session))
def teardown_method(self, method):
shutil.rmtree(self.config_dir)
class TestSessionFlow(SessionTestBase):
"""
These tests start with an existing session created in `setup_method()`.
@ -191,13 +212,7 @@ class TestSession(SessionTestBase):
os.chdir(cwd)
class TestExpiredCookies:
def setup_method(self, method):
self.config_dir = mk_config_dir()
def teardown_method(self, method):
shutil.rmtree(self.config_dir)
class TestExpiredCookies(CookieTestBase):
@pytest.mark.parametrize(
argnames=['initial_cookie', 'expired_cookie'],
@ -213,29 +228,16 @@ class TestExpiredCookies:
assert expired_cookie not in session.cookies
def test_expired_cookies(self, httpbin):
orig_session = {
'cookies': {
'to_expire': {
'value': 'foo'
},
'to_stay': {
'value': 'foo'
},
}
}
session_path = self.config_dir / 'test-session.json'
session_path.write_text(json.dumps(orig_session))
r = http(
'--session', str(session_path),
'--session', str(self.session_path),
'--print=H',
httpbin.url + '/cookies/delete?to_expire',
httpbin.url + '/cookies/delete?cookie2',
)
assert 'Cookie: to_expire=foo; to_stay=foo' in r
assert 'Cookie: cookie1=foo; cookie2=foo' in r
updated_session = json.loads(session_path.read_text())
assert 'to_stay' in updated_session['cookies']
assert 'to_expire' not in updated_session['cookies']
updated_session = json.loads(self.session_path.read_text())
assert 'cookie1' in updated_session['cookies']
assert 'cookie2' not in updated_session['cookies']
@pytest.mark.parametrize(
argnames=['headers', 'now', 'expected_expired'],
@ -277,3 +279,90 @@ class TestExpiredCookies:
)
def test_get_expired_cookies_manages_multiple_cookie_headers(self, headers, now, expected_expired):
assert get_expired_cookies(headers, now=now) == expected_expired
class TestCookieStorage(CookieTestBase):
@pytest.mark.parametrize(
argnames=['new_cookies', 'new_cookies_dict', 'expected'],
argvalues=[(
'new=bar',
{'new': 'bar'},
'cookie1=foo; cookie2=foo; new=bar'
),
(
'new=bar;chocolate=milk',
{'new': 'bar', 'chocolate': 'milk'},
'chocolate=milk; cookie1=foo; cookie2=foo; new=bar'
),
(
'new=bar; chocolate=milk',
{'new': 'bar', 'chocolate': 'milk'},
'chocolate=milk; cookie1=foo; cookie2=foo; new=bar'
),
(
'new=bar;; chocolate=milk;;;',
{'new': 'bar', 'chocolate': 'milk'},
'cookie1=foo; cookie2=foo; new=bar'
),
(
'new=bar; chocolate=milk;;;',
{'new': 'bar', 'chocolate': 'milk'},
'chocolate=milk; cookie1=foo; cookie2=foo; new=bar'
)
]
)
def test_existing_and_new_cookies_sent_in_request(self, new_cookies, new_cookies_dict, expected, httpbin):
r = http(
'--session', str(self.session_path),
'--print=H',
httpbin.url,
'Cookie:' + new_cookies,
)
# Note: cookies in response are in alphabetical order
assert 'Cookie: ' + expected in r
updated_session = json.loads(self.session_path.read_text())
for name, value in new_cookies_dict.items():
assert name, value in updated_session['cookies']
assert 'Cookie' not in updated_session['headers']
@pytest.mark.parametrize(
argnames=['cli_cookie', 'set_cookie', 'expected'],
argvalues=[(
'',
'/cookies/set/cookie1/bar',
'bar'
),
(
'cookie1=not_foo',
'/cookies/set/cookie1/bar',
'bar'
),
(
'cookie1=not_foo',
'',
'not_foo'
),
(
'',
'',
'foo'
)
]
)
def test_cookie_storage_priority(self, cli_cookie, set_cookie, expected, httpbin):
"""
Expected order of priority for cookie storage in session file:
1. set-cookie (from server)
2. command line arg
3. cookie already stored in session file
"""
r = http(
'--session', str(self.session_path),
httpbin.url + set_cookie,
'Cookie:' + cli_cookie,
)
updated_session = json.loads(self.session_path.read_text())
assert updated_session['cookies']['cookie1']['value'] == expected