Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cookie filter #799

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e84757a
Implemented simple sending cookie domain filter
panda73111 Feb 22, 2016
67f278d
Added tests for simple sending cookie domain filter
panda73111 Feb 22, 2016
eb8ff5c
Fixed flake8 errors
panda73111 Feb 22, 2016
1def5b0
Using ipaddress module to determine if a netloc is an IP address
panda73111 Feb 22, 2016
fbe82c4
Added workaround somewhere else commented "use dict method because Si…
panda73111 Feb 22, 2016
4bd4a4e
Fixed errors with ports in URLs by using hostname instead of netloc; …
panda73111 Feb 23, 2016
87bad1e
Fixed not accepting cookies from IPs
panda73111 Feb 23, 2016
3088e09
Removed impossible condition
panda73111 Feb 23, 2016
c7a9efe
Changed an obsolete test, where explicit cookie sharing is now required
panda73111 Feb 23, 2016
8720b8e
Added tests for filtering of received cookies
panda73111 Feb 23, 2016
f8812d5
Implemented host-only-flag
panda73111 Feb 23, 2016
90bb61b
Refactoring; Moved new code over to the new class helpers.SessionCook…
panda73111 Feb 24, 2016
7a19859
Split the tests into testing calls to SessionCookieStore and the filt…
panda73111 Feb 24, 2016
5d5dd5e
Modified test_cookie_store_usage, now using mock.patch
panda73111 Feb 29, 2016
0d3c2be
Renamed SessionCookieStore to CookieJar
panda73111 Feb 29, 2016
1d28248
Implemented filtering by secure-flag
panda73111 Feb 29, 2016
a648d14
Added test for secure-flag
panda73111 Feb 29, 2016
bb559cd
Added rudimentary test for is_ip_address()
panda73111 Feb 29, 2016
cdf703f
Implemented filtering by path-attribute
panda73111 Mar 18, 2016
1a409ea
Added tests for path-attribute
panda73111 Mar 18, 2016
3be84c2
Added test for path-attributes of received cookies
panda73111 Mar 18, 2016
4959234
Using TestCase's assertEqual()
panda73111 Mar 18, 2016
2d21ff4
Implemented expires- and max-age-attribute
panda73111 Mar 19, 2016
2832ea4
Added tests for expires- and max-age-attribute
panda73111 Mar 19, 2016
1cfb2c6
Passing the ClientSession's loop through to its CookieJar
panda73111 Mar 19, 2016
7917132
Added more tests
panda73111 Apr 13, 2016
f8c0e9a
Merge remote-tracking branch 'origin/master' into cookie_filter
panda73111 Apr 24, 2016
f6a5da4
Fixed flake8 "Missing whitespace around operator"
panda73111 Apr 24, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import sys
import traceback
import warnings
import re
import http.cookies
import urllib.parse

Expand All @@ -24,6 +25,8 @@

PY_35 = sys.version_info >= (3, 5)

IPV4_PATTERN = re.compile(r"(\d{1,3}\.){3}\d{1,3}")


class ClientSession:
"""First-class interface for making HTTP requests."""
Expand Down Expand Up @@ -172,10 +175,13 @@ def _request(self, method, url, *,
skip_headers.add(upstr(i))

while True:

cookies = self._filter_cookies(url)

req = self._request_class(
method, url, params=params, headers=headers,
skip_auto_headers=skip_headers, data=data,
cookies=self.cookies, encoding=encoding,
cookies=cookies, encoding=encoding,
auth=auth, version=version, compress=compress, chunked=chunked,
expect100=expect100,
loop=self._loop, response_class=self._response_class)
Expand Down Expand Up @@ -353,10 +359,58 @@ def _update_cookies(self, cookies):
if isinstance(value, http.cookies.Morsel):
# use dict method because SimpleCookie class modifies value
# before Python 3.4
domain = value["domain"]
if domain.startswith("."):
value["domain"] = domain[1:]
dict.__setitem__(self.cookies, name, value)
else:
self.cookies[name] = value

def _filter_cookies(self, url):
"""Returns this session's cookies filtered by their attributes"""
# TODO: filter by 'expires', 'path', ...
netloc = urllib.parse.urlsplit(url).netloc
is_ip = IPV4_PATTERN.match(netloc) is not None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ipaddress.IPAddress(netloc) to support both IPv4 and IPv6 in the right way without regexps (which accepts invalid IPv4 addresses).


filtered = http.cookies.SimpleCookie()

for name, morsel in self.cookies.items():
morsel_domain = morsel["domain"]

if is_ip and morsel_domain:
# not requesting from a domain, don't send cookies that aren't shared
continue

# Copy cookies with matching or empty (shared) domain
if not morsel_domain or self._is_domain_match(morsel_domain, netloc):
filtered[name] = morsel

return filtered

@staticmethod
def _is_domain_match(domain_string, string):
"""Implements domain matching according to RFC 6265"""
if domain_string == string:
return True

if not string.endswith(domain_string):
return False

rest = string[:-len(domain_string)]

if not rest:
return False

if rest[-1] != ".":
return False

netloc = urllib.parse.urlsplit(string).netloc
is_ip = IPV4_PATTERN.match(netloc) is not None
if is_ip:
return False

return True

def _prepare_headers(self, headers):
""" Add default headers and transform it to CIMultiDict
"""
Expand Down
96 changes: 96 additions & 0 deletions tests/test_client_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import gc
import re
import types
import http.cookies
from unittest import mock

import aiohttp
import pytest
from aiohttp import test_utils
from aiohttp.client import ClientSession
from aiohttp.connector import BaseConnector, TCPConnector
from aiohttp.multidict import CIMultiDict, MultiDict
Expand Down Expand Up @@ -368,3 +370,97 @@ def test_request_ctx_manager_props(loop):
assert isinstance(ctx_mgr.gi_frame, types.FrameType)
assert not ctx_mgr.gi_running
assert isinstance(ctx_mgr.gi_code, types.CodeType)


@pytest.fixture
def get_with_cookies(create_session):
cookie_string = """
shared-cookie=first;
domain-cookie=second; domain=example.com;
subdomain1-cookie=third; domain=test1.example.com;
subdomain2-cookie=fourth; domain=test2.example.com;
dotted-domain-cookie=fifth; domain=.example.com;
different-domain-cookie=sixth; domain=different.org;
"""

err = NotImplementedError()

req = mock.Mock()
req_factory = mock.Mock(return_value=req)

req.send = mock.Mock(side_effect=err)

session = create_session(
request_class=req_factory,
cookies=http.cookies.SimpleCookie(cookie_string))

@asyncio.coroutine
def create_connection(req):
# return self.transport, self.protocol
return mock.Mock(), mock.Mock()
session._connector._create_connection = create_connection

return session.get, req_factory


@pytest.fixture
def send_cookie_request(get_with_cookies):
get, req = get_with_cookies

@asyncio.coroutine
def maker(*args, **kwargs):
with pytest.raises(NotImplementedError):
yield from get(*args, **kwargs)

cookies = req.call_args[1]["cookies"]
return cookies
return maker


@pytest.mark.run_loop
def test_cookie_domain_filter_ip(send_cookie_request):
cookies = yield from send_cookie_request("http://1.2.3.4/")
assert set(cookies.keys()) == {
"shared-cookie"
}


@pytest.mark.run_loop
def test_cookie_domain_filter_same_host(send_cookie_request):
cookies = yield from send_cookie_request("http://example.com/")
assert set(cookies.keys()) == {
"shared-cookie",
"domain-cookie",
"dotted-domain-cookie"
}


@pytest.mark.run_loop
def test_cookie_domain_filter_same_host_and_subdomain(send_cookie_request):
cookies = yield from send_cookie_request("http://test1.example.com/")
assert set(cookies.keys()) == {
"shared-cookie",
"domain-cookie",
"subdomain1-cookie",
"dotted-domain-cookie"
}


@pytest.mark.run_loop
def test_cookie_domain_filter_same_host_different_subdomain(send_cookie_request):
cookies = yield from send_cookie_request("http://different.example.com/")
assert set(cookies.keys()) == {
"shared-cookie",
"domain-cookie",
"dotted-domain-cookie"
}


@pytest.mark.run_loop
def test_cookie_domain_filter_different_host(send_cookie_request):
cookies = yield from send_cookie_request("http://different.org/")
assert set(cookies.keys()) == {
"shared-cookie",
"different-domain-cookie"
}