httpie-cli/httpie/sessions.py

162 lines
4.8 KiB
Python
Raw Normal View History

"""
Persistent, JSON-serialized sessions.
"""
import os
import re
from http.cookies import SimpleCookie
from pathlib import Path
from typing import Iterable, Optional, Union
2019-08-29 08:53:56 +02:00
from urllib.parse import urlsplit
from requests.auth import AuthBase
2020-06-15 23:02:16 +02:00
from requests.cookies import RequestsCookieJar, create_cookie
from .cli.dicts import HTTPHeadersDict
from .config import BaseConfigDict, DEFAULT_CONFIG_DIR
from .plugins.registry import plugin_manager
2012-09-17 02:15:00 +02:00
SESSIONS_DIR_NAME = 'sessions'
DEFAULT_SESSIONS_DIR = DEFAULT_CONFIG_DIR / SESSIONS_DIR_NAME
VALID_SESSION_NAME_PATTERN = re.compile('^[a-zA-Z0-9_.-]+$')
# Request headers starting with these prefixes won't be stored in sessions.
# They are specific to each request.
2019-12-03 19:09:09 +01:00
# <https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Requests>
SESSION_IGNORED_HEADER_PREFIXES = ['Content-', 'If-']
def get_httpie_session(
config_dir: Path,
session_name: str,
host: Optional[str],
url: str,
) -> 'Session':
if os.path.sep in session_name:
path = os.path.expanduser(session_name)
else:
hostname = host or urlsplit(url).netloc.split('@')[-1]
if not hostname:
# HACK/FIXME: httpie-unixsocket's URLs have no hostname.
hostname = 'localhost'
# host:port => host_port
hostname = hostname.replace(':', '_')
path = (
config_dir / SESSIONS_DIR_NAME / hostname / f'{session_name}.json'
)
session = Session(path)
2012-08-19 04:58:14 +02:00
session.load()
return session
class Session(BaseConfigDict):
helpurl = 'https://httpie.io/docs#sessions'
2012-12-01 18:16:00 +01:00
about = 'HTTPie session file'
def __init__(self, path: Union[str, Path]):
super().__init__(path=Path(path))
2012-08-19 04:58:14 +02:00
self['headers'] = {}
self['cookies'] = {}
2012-12-01 18:16:00 +01:00
self['auth'] = {
'type': None,
2012-12-11 12:54:34 +01:00
'username': None,
'password': None
2012-12-01 18:16:00 +01:00
}
def update_headers(self, request_headers: HTTPHeadersDict):
"""
Update the session headers with the request ones while ignoring
certain name prefixes.
"""
2019-09-01 21:15:39 +02:00
headers = self.headers
for name, value in request_headers.copy().items():
if value is None:
continue # Ignore explicitly unset headers
if type(value) is not str:
value = value.decode()
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:
if name.lower().startswith(prefix.lower()):
break
else:
2019-09-01 21:15:39 +02:00
headers[name] = value
self['headers'] = dict(headers)
@property
def headers(self) -> HTTPHeadersDict:
return HTTPHeadersDict(self['headers'])
@property
def cookies(self) -> RequestsCookieJar:
jar = RequestsCookieJar()
for name, cookie_dict in self['cookies'].items():
2012-08-21 15:45:22 +02:00
jar.set_cookie(create_cookie(
name, cookie_dict.pop('value'), **cookie_dict))
jar.clear_expired_cookies()
return jar
@cookies.setter
def cookies(self, jar: RequestsCookieJar):
2021-09-28 12:54:16 +02:00
# <https://docs.python.org/3/library/cookielib.html#cookie-objects>
2013-01-22 20:03:28 +01:00
stored_attrs = ['value', 'path', 'secure', 'expires']
self['cookies'] = {}
2013-03-20 10:45:56 +01:00
for cookie in jar:
2017-12-28 18:15:17 +01:00
self['cookies'][cookie.name] = {
attname: getattr(cookie, attname)
2013-03-20 16:07:23 +01:00
for attname in stored_attrs
2017-12-28 18:15:17 +01:00
}
@property
def auth(self) -> Optional[AuthBase]:
auth = self.get('auth', None)
2012-12-01 18:16:00 +01:00
if not auth or not auth['type']:
2012-12-11 12:54:34 +01:00
return
plugin = plugin_manager.get_auth_plugin(auth['type'])()
credentials = {'username': None, 'password': None}
try:
# New style
plugin.raw_auth = auth['raw_auth']
except KeyError:
# Old style
credentials = {
'username': auth['username'],
'password': auth['password'],
}
else:
if plugin.auth_parse:
from .cli.argtypes import parse_auth
parsed = parse_auth(plugin.raw_auth)
credentials = {
'username': parsed.key,
'password': parsed.value,
}
return plugin.get_auth(**credentials)
@auth.setter
def auth(self, auth: dict):
2019-08-30 09:56:50 +02:00
assert {'type', 'raw_auth'} == auth.keys()
2013-09-21 23:46:15 +02:00
self['auth'] = auth
def remove_cookies(self, names: Iterable[str]):
for name in names:
if name in self['cookies']:
del self['cookies'][name]