1
0
mirror of https://github.com/httpie/cli.git synced 2025-08-10 22:42:05 +02:00

Implement new style cookies

This commit is contained in:
Batuhan Taskaya
2022-02-01 12:14:24 +03:00
parent b5623ccc87
commit 65ab7d5caa
27 changed files with 1406 additions and 117 deletions

View File

@@ -4,7 +4,11 @@ import socket
import pytest
from pytest_httpbin import certs
from .utils import HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN, HTTPBIN_WITH_CHUNKED_SUPPORT
from .utils import ( # noqa
HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN,
HTTPBIN_WITH_CHUNKED_SUPPORT,
mock_env
)
from .utils.plugins_cli import ( # noqa
broken_plugin,
dummy_plugin,

View File

@@ -1,6 +1,9 @@
"""Test data"""
import json
from pathlib import Path
from typing import Optional, Dict, Any
import httpie
from httpie.encoding import UTF8
from httpie.output.formatters.xml import pretty_xml, parse_xml
@@ -19,10 +22,20 @@ FILE_PATH = FIXTURES_ROOT / 'test.txt'
JSON_FILE_PATH = FIXTURES_ROOT / 'test.json'
JSON_WITH_DUPE_KEYS_FILE_PATH = FIXTURES_ROOT / 'test_with_dupe_keys.json'
BIN_FILE_PATH = FIXTURES_ROOT / 'test.bin'
XML_FILES_PATH = FIXTURES_ROOT / 'xmldata'
XML_FILES_VALID = list((XML_FILES_PATH / 'valid').glob('*_raw.xml'))
XML_FILES_INVALID = list((XML_FILES_PATH / 'invalid').glob('*.xml'))
SESSION_FILES_PATH = FIXTURES_ROOT / 'session_data'
SESSION_FILES_OLD = sorted((SESSION_FILES_PATH / 'old').glob('*.json'))
SESSION_FILES_NEW = sorted((SESSION_FILES_PATH / 'new').glob('*.json'))
SESSION_VARIABLES = {
'__version__': httpie.__version__,
'__host__': 'null',
}
FILE_PATH_ARG = patharg(FILE_PATH)
BIN_FILE_PATH_ARG = patharg(BIN_FILE_PATH)
JSON_FILE_PATH_ARG = patharg(JSON_FILE_PATH)
@@ -40,3 +53,14 @@ BIN_FILE_CONTENT = BIN_FILE_PATH.read_bytes()
UNICODE = FILE_CONTENT
XML_DATA_RAW = '<?xml version="1.0" encoding="utf-8"?><root><e>text</e></root>'
XML_DATA_FORMATTED = pretty_xml(parse_xml(XML_DATA_RAW))
def read_session_file(session_file: Path, *, extra_variables: Optional[Dict[str, str]] = None) -> Any:
with open(session_file) as stream:
data = stream.read()
session_vars = {**SESSION_VARIABLES, **(extra_variables or {})}
for variable, value in session_vars.items():
data = data.replace(variable, value)
return json.loads(data)

View File

@@ -0,0 +1,31 @@
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.io/docs#sessions",
"httpie": "__version__"
},
"auth": {
"password": null,
"type": null,
"username": null
},
"cookies": [
{
"domain": __host__,
"expires": null,
"name": "baz",
"path": "/",
"secure": false,
"value": "quux"
},
{
"domain": __host__,
"expires": null,
"name": "foo",
"path": "/",
"secure": false,
"value": "bar"
}
],
"headers": {}
}

View File

@@ -0,0 +1,31 @@
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.io/docs#sessions",
"httpie": "__version__"
},
"auth": {
"password": null,
"type": null,
"username": null
},
"cookies": [
{
"domain": __host__,
"expires": null,
"name": "baz",
"path": "/",
"secure": false,
"value": "quux"
},
{
"domain": __host__,
"expires": null,
"name": "foo",
"path": "/",
"secure": false,
"value": "bar"
}
],
"headers": {}
}

View File

@@ -0,0 +1,33 @@
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.io/docs#sessions",
"httpie": "__version__"
},
"auth": {
"raw_auth": "foo:bar",
"type": "basic"
},
"cookies": [
{
"domain": __host__,
"expires": null,
"name": "baz",
"path": "/",
"secure": false,
"value": "quux"
},
{
"domain": __host__,
"expires": null,
"name": "foo",
"path": "/",
"secure": false,
"value": "bar"
}
],
"headers": {
"X-Data": "value",
"X-Foo": "bar"
}
}

View File

@@ -0,0 +1,14 @@
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.io/docs#sessions",
"httpie": "__version__"
},
"auth": {
"password": null,
"type": null,
"username": null
},
"cookies": [],
"headers": {}
}

View File

@@ -0,0 +1,14 @@
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.io/docs#sessions",
"httpie": "__version__"
},
"auth": {
"password": null,
"type": null,
"username": null
},
"cookies": [],
"headers": {}
}

View File

@@ -0,0 +1,27 @@
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.io/docs#sessions",
"httpie": "3.0.2"
},
"auth": {
"password": null,
"type": null,
"username": null
},
"cookies": {
"baz": {
"expires": null,
"path": "/",
"secure": false,
"value": "quux"
},
"foo": {
"expires": null,
"path": "/",
"secure": false,
"value": "bar"
}
},
"headers": {}
}

View File

@@ -0,0 +1,27 @@
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.io/docs#sessions",
"httpie": "2.7.0.dev0"
},
"auth": {
"password": null,
"type": null,
"username": null
},
"cookies": {
"baz": {
"expires": null,
"path": "/",
"secure": false,
"value": "quux"
},
"foo": {
"expires": null,
"path": "/",
"secure": false,
"value": "bar"
}
},
"headers": {}
}

View File

@@ -0,0 +1,29 @@
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.io/docs#sessions",
"httpie": "3.0.2"
},
"auth": {
"raw_auth": "foo:bar",
"type": "basic"
},
"cookies": {
"baz": {
"expires": null,
"path": "/",
"secure": false,
"value": "quux"
},
"foo": {
"expires": null,
"path": "/",
"secure": false,
"value": "bar"
}
},
"headers": {
"X-Data": "value",
"X-Foo": "bar"
}
}

View File

@@ -0,0 +1,14 @@
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.io/docs#sessions",
"httpie": "3.0.2"
},
"auth": {
"password": null,
"type": null,
"username": null
},
"cookies": {},
"headers": {}
}

View File

@@ -0,0 +1,14 @@
{
"__meta__": {
"about": "HTTPie session file",
"help": "https://httpie.io/docs#sessions",
"httpie": "3.0.2"
},
"auth": {
"password": null,
"type": null,
"username": null
},
"cookies": [],
"headers": {}
}

View File

@@ -0,0 +1,262 @@
import pytest
from .utils import http
@pytest.fixture
def remote_httpbin(httpbin_with_chunked_support):
return httpbin_with_chunked_support
def _stringify(fixture):
return fixture + ''
@pytest.mark.parametrize('instance', [
pytest.lazy_fixture('httpbin'),
pytest.lazy_fixture('remote_httpbin'),
])
def test_explicit_user_set_cookie(httpbin, instance):
# User set cookies ARE NOT persisted within redirects
# when there is no session, even on the same domain.
r = http(
'--follow',
httpbin + '/redirect-to',
f'url=={_stringify(instance)}/cookies',
'Cookie:a=b'
)
assert r.json == {'cookies': {}}
@pytest.mark.parametrize('instance', [
pytest.lazy_fixture('httpbin'),
pytest.lazy_fixture('remote_httpbin'),
])
def test_explicit_user_set_cookie_in_session(tmp_path, httpbin, instance):
# User set cookies ARE persisted within redirects
# when there is A session, even on the same domain.
r = http(
'--follow',
'--session',
str(tmp_path / 'session.json'),
httpbin + '/redirect-to',
f'url=={_stringify(instance)}/cookies',
'Cookie:a=b'
)
assert r.json == {'cookies': {'a': 'b'}}
@pytest.mark.parametrize('instance', [
pytest.lazy_fixture('httpbin'),
pytest.lazy_fixture('remote_httpbin'),
])
def test_saved_user_set_cookie_in_session(tmp_path, httpbin, instance):
# User set cookies ARE persisted within redirects
# when there is A session, even on the same domain.
http(
'--follow',
'--session',
str(tmp_path / 'session.json'),
httpbin + '/get',
'Cookie:a=b'
)
r = http(
'--follow',
'--session',
str(tmp_path / 'session.json'),
httpbin + '/redirect-to',
f'url=={_stringify(instance)}/cookies',
)
assert r.json == {'cookies': {'a': 'b'}}
@pytest.mark.parametrize('instance', [
pytest.lazy_fixture('httpbin'),
pytest.lazy_fixture('remote_httpbin'),
])
@pytest.mark.parametrize('session', [True, False])
def test_explicit_user_set_headers(httpbin, tmp_path, instance, session):
# User set headers ARE persisted within redirects
# even on different domains domain with or without
# an active session.
session_args = []
if session:
session_args.extend([
'--session',
str(tmp_path / 'session.json')
])
r = http(
'--follow',
*session_args,
httpbin + '/redirect-to',
f'url=={_stringify(instance)}/get',
'X-Custom-Header:value'
)
assert 'X-Custom-Header' in r.json['headers']
@pytest.mark.parametrize('session', [True, False])
def test_server_set_cookie_on_redirect_same_domain(tmp_path, httpbin, session):
# Server set cookies ARE persisted on the same domain
# when they are forwarded.
session_args = []
if session:
session_args.extend([
'--session',
str(tmp_path / 'session.json')
])
r = http(
'--follow',
*session_args,
httpbin + '/cookies/set/a/b',
)
assert r.json['cookies'] == {'a': 'b'}
@pytest.mark.parametrize('session', [True, False])
def test_server_set_cookie_on_redirect_different_domain(tmp_path, http_server, httpbin, session):
# Server set cookies ARE persisted on different domains
# when they are forwarded.
session_args = []
if session:
session_args.extend([
'--session',
str(tmp_path / 'session.json')
])
r = http(
'--follow',
*session_args,
http_server + '/cookies/set-and-redirect',
f"X-Redirect-To:{httpbin + '/cookies'}",
'X-Cookies:a=b'
)
assert r.json['cookies'] == {'a': 'b'}
def test_saved_session_cookies_on_same_domain(tmp_path, httpbin):
# Saved session cookies ARE persisted when making a new
# request to the same domain.
http(
'--session',
str(tmp_path / 'session.json'),
httpbin + '/cookies/set/a/b'
)
r = http(
'--session',
str(tmp_path / 'session.json'),
httpbin + '/cookies'
)
assert r.json == {'cookies': {'a': 'b'}}
def test_saved_session_cookies_on_different_domain(tmp_path, httpbin, remote_httpbin):
# Saved session cookies ARE persisted when making a new
# request to a different domain.
http(
'--session',
str(tmp_path / 'session.json'),
httpbin + '/cookies/set/a/b'
)
r = http(
'--session',
str(tmp_path / 'session.json'),
remote_httpbin + '/cookies'
)
assert r.json == {'cookies': {}}
@pytest.mark.parametrize('initial_domain, first_request_domain, second_request_domain, expect_cookies', [
(
# Cookies are set by Domain A
# Initial domain is Domain A
# Redirected domain is Domain A
pytest.lazy_fixture('httpbin'),
pytest.lazy_fixture('httpbin'),
pytest.lazy_fixture('httpbin'),
True,
),
(
# Cookies are set by Domain A
# Initial domain is Domain B
# Redirected domain is Domain B
pytest.lazy_fixture('httpbin'),
pytest.lazy_fixture('remote_httpbin'),
pytest.lazy_fixture('remote_httpbin'),
False,
),
(
# Cookies are set by Domain A
# Initial domain is Domain A
# Redirected domain is Domain B
pytest.lazy_fixture('httpbin'),
pytest.lazy_fixture('httpbin'),
pytest.lazy_fixture('remote_httpbin'),
False,
),
(
# Cookies are set by Domain A
# Initial domain is Domain B
# Redirected domain is Domain A
pytest.lazy_fixture('httpbin'),
pytest.lazy_fixture('remote_httpbin'),
pytest.lazy_fixture('httpbin'),
True,
),
])
def test_saved_session_cookies_on_redirect(tmp_path, initial_domain, first_request_domain, second_request_domain, expect_cookies):
http(
'--session',
str(tmp_path / 'session.json'),
initial_domain + '/cookies/set/a/b'
)
r = http(
'--session',
str(tmp_path / 'session.json'),
'--follow',
first_request_domain + '/redirect-to',
f'url=={_stringify(second_request_domain)}/cookies'
)
if expect_cookies:
expected_data = {'cookies': {'a': 'b'}}
else:
expected_data = {'cookies': {}}
assert r.json == expected_data
def test_saved_session_cookie_pool(tmp_path, httpbin, remote_httpbin):
http(
'--session',
str(tmp_path / 'session.json'),
httpbin + '/cookies/set/a/b'
)
http(
'--session',
str(tmp_path / 'session.json'),
remote_httpbin + '/cookies/set/a/c'
)
http(
'--session',
str(tmp_path / 'session.json'),
remote_httpbin + '/cookies/set/b/d'
)
response = http(
'--session',
str(tmp_path / 'session.json'),
httpbin + '/cookies'
)
assert response.json['cookies'] == {'a': 'b'}
response = http(
'--session',
str(tmp_path / 'session.json'),
remote_httpbin + '/cookies'
)
assert response.json['cookies'] == {'a': 'c', 'b': 'd'}

125
tests/test_httpie_cli.py Normal file
View File

@@ -0,0 +1,125 @@
import pytest
import shutil
import json
from httpie.sessions import SESSIONS_DIR_NAME
from httpie.status import ExitStatus
from tests.utils import DUMMY_HOST, httpie
from tests.fixtures import SESSION_FILES_PATH, SESSION_FILES_NEW, SESSION_FILES_OLD, read_session_file
OLD_SESSION_FILES_PATH = SESSION_FILES_PATH / 'old'
@pytest.mark.requires_installation
def test_plugins_cli_error_message_without_args():
# No arguments
result = httpie(no_debug=True)
assert result.exit_status == ExitStatus.ERROR
assert 'usage: ' in result.stderr
assert 'specify one of these' in result.stderr
assert 'please use the http/https commands:' in result.stderr
@pytest.mark.parametrize(
'example',
[
'pie.dev/get',
'DELETE localhost:8000/delete',
'POST pie.dev/post header:value a=b header_2:value x:=1',
],
)
@pytest.mark.requires_installation
def test_plugins_cli_error_messages_with_example(example):
result = httpie(*example.split(), no_debug=True)
assert result.exit_status == ExitStatus.ERROR
assert 'usage: ' in result.stderr
assert f'http {example}' in result.stderr
assert f'https {example}' in result.stderr
@pytest.mark.parametrize(
'example',
[
'cli',
'plugins',
'cli foo',
'plugins unknown',
'plugins unknown.com A:B c=d',
'unknown.com UNPARSABLE????SYNTAX',
],
)
@pytest.mark.requires_installation
def test_plugins_cli_error_messages_invalid_example(example):
result = httpie(*example.split(), no_debug=True)
assert result.exit_status == ExitStatus.ERROR
assert 'usage: ' in result.stderr
assert f'http {example}' not in result.stderr
assert f'https {example}' not in result.stderr
HTTPIE_CLI_SESSIONS_UPGRADE_OPTIONS = [
(
# Default settings
[],
{'__host__': json.dumps(None)},
),
(
# When --bind-cookies is applied, the __host__ becomes DUMMY_URL.
['--bind-cookies'],
{'__host__': json.dumps(DUMMY_HOST)},
),
]
@pytest.mark.parametrize(
'old_session_file, new_session_file', zip(SESSION_FILES_OLD, SESSION_FILES_NEW)
)
@pytest.mark.parametrize(
'extra_args, extra_variables',
HTTPIE_CLI_SESSIONS_UPGRADE_OPTIONS,
)
def test_httpie_sessions_upgrade(tmp_path, old_session_file, new_session_file, extra_args, extra_variables):
session_path = tmp_path / 'session.json'
shutil.copyfile(old_session_file, session_path)
result = httpie(
'cli', 'sessions', 'upgrade', *extra_args, DUMMY_HOST, str(session_path)
)
assert result.exit_status == ExitStatus.SUCCESS
assert read_session_file(session_path) == read_session_file(
new_session_file, extra_variables=extra_variables
)
def test_httpie_sessions_upgrade_on_non_existent_file(tmp_path):
session_path = tmp_path / 'session.json'
result = httpie('cli', 'sessions', 'upgrade', DUMMY_HOST, str(session_path))
assert result.exit_status == ExitStatus.ERROR
assert 'does not exist' in result.stderr
@pytest.mark.parametrize(
'extra_args, extra_variables',
HTTPIE_CLI_SESSIONS_UPGRADE_OPTIONS,
)
def test_httpie_sessions_upgrade_all(tmp_path, mock_env, extra_args, extra_variables):
mock_env._create_temp_config_dir = False
mock_env.config_dir = tmp_path / "config"
session_dir = mock_env.config_dir / SESSIONS_DIR_NAME / DUMMY_HOST
session_dir.mkdir(parents=True)
for original_session_file in SESSION_FILES_OLD:
shutil.copy(original_session_file, session_dir)
result = httpie(
'cli', 'sessions', 'upgrade-all', *extra_args, env=mock_env
)
assert result.exit_status == ExitStatus.SUCCESS
for refactored_session_file, expected_session_file in zip(
sorted(session_dir.glob("*.json")),
SESSION_FILES_NEW
):
assert read_session_file(refactored_session_file) == read_session_file(
expected_session_file, extra_variables=extra_variables
)

View File

@@ -1,7 +1,6 @@
import pytest
from httpie.status import ExitStatus
from tests.utils import httpie
from tests.utils.plugins_cli import parse_listing
@@ -149,45 +148,3 @@ def test_broken_plugins(httpie_plugins, httpie_plugins_success, dummy_plugin, br
# No warning now, since it is uninstalled.
data = parse_listing(httpie_plugins_success('list'))
assert len(data) == 1
@pytest.mark.requires_installation
def test_plugins_cli_error_message_without_args():
# No arguments
result = httpie(no_debug=True)
assert result.exit_status == ExitStatus.ERROR
assert 'usage: ' in result.stderr
assert 'specify one of these' in result.stderr
assert 'please use the http/https commands:' in result.stderr
@pytest.mark.parametrize(
'example', [
'pie.dev/get',
'DELETE localhost:8000/delete',
'POST pie.dev/post header:value a=b header_2:value x:=1'
]
)
@pytest.mark.requires_installation
def test_plugins_cli_error_messages_with_example(example):
result = httpie(*example.split(), no_debug=True)
assert result.exit_status == ExitStatus.ERROR
assert 'usage: ' in result.stderr
assert f'http {example}' in result.stderr
assert f'https {example}' in result.stderr
@pytest.mark.parametrize(
'example', [
'plugins unknown',
'plugins unknown.com A:B c=d',
'unknown.com UNPARSABLE????SYNTAX',
]
)
@pytest.mark.requires_installation
def test_plugins_cli_error_messages_invalid_example(example):
result = httpie(*example.split(), no_debug=True)
assert result.exit_status == ExitStatus.ERROR
assert 'usage: ' in result.stderr
assert f'http {example}' not in result.stderr
assert f'https {example}' not in result.stderr

View File

@@ -1,12 +1,16 @@
import json
import os
import shutil
from contextlib import contextmanager
from datetime import datetime
from unittest import mock
from pathlib import Path
from typing import Iterator
import pytest
from .fixtures import FILE_PATH_ARG, UNICODE
from httpie.context import Environment
from httpie.encoding import UTF8
from httpie.plugins import AuthPlugin
from httpie.plugins.builtin import HTTPBasicAuth
@@ -14,7 +18,7 @@ from httpie.plugins.registry import plugin_manager
from httpie.sessions import Session
from httpie.utils import get_expired_cookies
from .test_auth_plugins import basic_auth
from .utils import HTTP_OK, MockEnvironment, http, mk_config_dir
from .utils import DUMMY_HOST, HTTP_OK, MockEnvironment, http, mk_config_dir
from base64 import b64encode
@@ -203,9 +207,9 @@ class TestSession(SessionTestBase):
"""
self.start_session(httpbin)
session_data = {
"headers": {
"cookie": "...",
"zzz": "..."
'headers': {
'cookie': '...',
'zzz': '...'
}
}
session_path = self.config_dir / 'session-data.json'
@@ -307,7 +311,7 @@ class TestSession(SessionTestBase):
auth_type = 'test-prompted'
def get_auth(self, username=None, password=None):
basic_auth_header = "Basic " + b64encode(self.raw_auth.encode()).strip().decode('latin1')
basic_auth_header = 'Basic ' + b64encode(self.raw_auth.encode()).strip().decode('latin1')
return basic_auth(basic_auth_header)
plugin_manager.register(Plugin)
@@ -359,7 +363,7 @@ class TestSession(SessionTestBase):
)
updated_session = json.loads(self.session_path.read_text(encoding=UTF8))
assert updated_session['auth']['type'] == 'test-saved'
assert updated_session['auth']['raw_auth'] == "user:password"
assert updated_session['auth']['raw_auth'] == 'user:password'
plugin_manager.unregister(Plugin)
@@ -368,12 +372,12 @@ class TestExpiredCookies(CookieTestBase):
@pytest.mark.parametrize(
'initial_cookie, expired_cookie',
[
({'id': {'value': 123}}, 'id'),
({'id': {'value': 123}}, 'token')
({'id': {'value': 123}}, {'name': 'id'}),
({'id': {'value': 123}}, {'name': 'token'})
]
)
def test_removes_expired_cookies_from_session_obj(self, initial_cookie, expired_cookie, httpbin):
session = Session(self.config_dir)
def test_removes_expired_cookies_from_session_obj(self, initial_cookie, expired_cookie, httpbin, mock_env):
session = Session(self.config_dir, env=mock_env, session_id=None, bound_host=None)
session['cookies'] = initial_cookie
session.remove_cookies([expired_cookie])
assert expired_cookie not in session.cookies
@@ -524,3 +528,165 @@ class TestCookieStorage(CookieTestBase):
updated_session = json.loads(self.session_path.read_text(encoding=UTF8))
assert updated_session['cookies']['cookie1']['value'] == expected
@pytest.fixture
def basic_session(httpbin, tmp_path):
session_path = tmp_path / 'session.json'
http(
'--session', str(session_path),
httpbin + '/get'
)
return session_path
@contextmanager
def open_session(path: Path, env: Environment, read_only: bool = False) -> Iterator[Session]:
session = Session(path, env, session_id='test', bound_host=DUMMY_HOST)
session.load()
yield session
if not read_only:
session.save()
@contextmanager
def open_raw_session(path: Path, read_only: bool = False) -> None:
with open(path) as stream:
raw_session = json.load(stream)
yield raw_session
if not read_only:
with open(path, 'w') as stream:
json.dump(raw_session, stream)
def read_stderr(env: Environment) -> bytes:
env.stderr.seek(0)
stderr_data = env.stderr.read()
if isinstance(stderr_data, str):
return stderr_data.encode()
else:
return stderr_data
def test_old_session_version_saved_as_is(basic_session, mock_env):
with open_session(basic_session, mock_env) as session:
session['__meta__'] = {'httpie': '0.0.1'}
with open_session(basic_session, mock_env, read_only=True) as session:
assert session['__meta__']['httpie'] == '0.0.1'
def test_old_session_cookie_layout_warning(basic_session, mock_env):
with open_session(basic_session, mock_env) as session:
# Use the old layout & set a cookie
session['cookies'] = {}
session.cookies.set('foo', 'bar')
assert read_stderr(mock_env) == b''
with open_session(basic_session, mock_env, read_only=True) as session:
assert b'Outdated layout detected' in read_stderr(mock_env)
@pytest.mark.parametrize('cookies, expect_warning', [
# Old-style cookie format
(
# Without 'domain' set
{'foo': {'value': 'bar'}},
True
),
(
# With 'domain' set to empty string
{'foo': {'value': 'bar', 'domain': ''}},
True
),
(
# With 'domain' set to null
{'foo': {'value': 'bar', 'domain': None}},
False,
),
(
# With 'domain' set to a URL
{'foo': {'value': 'bar', 'domain': DUMMY_HOST}},
False,
),
# New style cookie format
(
# Without 'domain' set
[{'name': 'foo', 'value': 'bar'}],
False
),
(
# With 'domain' set to empty string
[{'name': 'foo', 'value': 'bar', 'domain': ''}],
False
),
(
# With 'domain' set to null
[{'name': 'foo', 'value': 'bar', 'domain': None}],
False,
),
(
# With 'domain' set to a URL
[{'name': 'foo', 'value': 'bar', 'domain': DUMMY_HOST}],
False,
),
])
def test_cookie_security_warnings_on_raw_cookies(basic_session, mock_env, cookies, expect_warning):
with open_raw_session(basic_session) as raw_session:
raw_session['cookies'] = cookies
with open_session(basic_session, mock_env, read_only=True):
warning = b'Outdated layout detected'
stderr = read_stderr(mock_env)
if expect_warning:
assert warning in stderr
else:
assert warning not in stderr
def test_old_session_cookie_layout_loading(basic_session, httpbin, mock_env):
with open_session(basic_session, mock_env) as session:
# Use the old layout & set a cookie
session['cookies'] = {}
session.cookies.set('foo', 'bar')
response = http(
'--session', str(basic_session),
httpbin + '/cookies'
)
assert response.json['cookies'] == {'foo': 'bar'}
@pytest.mark.parametrize('layout_type', [
dict, list
])
def test_session_cookie_layout_preservance(basic_session, mock_env, layout_type):
with open_session(basic_session, mock_env) as session:
session['cookies'] = layout_type()
session.cookies.set('foo', 'bar')
session.save()
with open_session(basic_session, mock_env, read_only=True) as session:
assert isinstance(session['cookies'], layout_type)
@pytest.mark.parametrize('layout_type', [
dict, list
])
def test_session_cookie_layout_preservance_on_new_cookies(basic_session, httpbin, mock_env, layout_type):
with open_session(basic_session, mock_env) as session:
session['cookies'] = layout_type()
session.cookies.set('foo', 'bar')
session.save()
http(
'--session', str(basic_session),
httpbin + '/cookies/set/baz/quux'
)
with open_session(basic_session, mock_env, read_only=True) as session:
assert isinstance(session['cookies'], layout_type)

View File

@@ -6,6 +6,8 @@ import time
import json
import tempfile
import warnings
import pytest
from contextlib import suppress
from io import BytesIO
from pathlib import Path
from typing import Any, Optional, Union, List, Iterable
@@ -16,6 +18,7 @@ import httpie.manager.__main__ as manager
from httpie.status import ExitStatus
from httpie.config import Config
from httpie.context import Environment
from httpie.utils import url_as_host
# pytest-httpbin currently does not support chunked requests:
@@ -39,6 +42,7 @@ HTTP_OK_COLOR = (
)
DUMMY_URL = 'http://this-should.never-resolve' # Note: URL never fetched
DUMMY_HOST = url_as_host(DUMMY_URL)
def strip_colors(colorized_msg: str) -> str:
@@ -187,6 +191,13 @@ class ExitStatusError(Exception):
pass
@pytest.fixture
def mock_env() -> MockEnvironment:
env = MockEnvironment(stdout_mode='')
yield env
env.cleanup()
def normalize_args(args: Iterable[Any]) -> List[str]:
return [str(arg) for arg in args]
@@ -201,7 +212,7 @@ def httpie(
status.
"""
env = kwargs.setdefault('env', MockEnvironment())
env = kwargs.setdefault('env', MockEnvironment(stdout_mode=''))
cli_args = ['httpie']
if not kwargs.pop('no_debug', False):
cli_args.append('--debug')
@@ -214,7 +225,16 @@ def httpie(
env.stdout.seek(0)
env.stderr.seek(0)
try:
response = StrCLIResponse(env.stdout.read())
output = env.stdout.read()
if isinstance(output, bytes):
with suppress(UnicodeDecodeError):
output = output.decode()
if isinstance(output, bytes):
response = BytesCLIResponse(output)
else:
response = StrCLIResponse(output)
response.stderr = env.stderr.read()
response.exit_status = exit_status
response.args = cli_args

View File

@@ -85,6 +85,19 @@ def status_custom_msg(handler):
handler.end_headers()
@TestHandler.handler('GET', '/cookies/set-and-redirect')
def set_cookie_and_redirect(handler):
handler.send_response(302)
redirect_to = handler.headers.get('X-Redirect-To', '/headers')
handler.send_header('Location', redirect_to)
raw_cookies = handler.headers.get('X-Cookies', 'a=b')
for cookie in raw_cookies.split(', '):
handler.send_header('Set-Cookie', cookie)
handler.end_headers()
@pytest.fixture(scope="function")
def http_server():
"""A custom HTTP server implementation for our tests, that is