Implement Array.splice method
[vadmium-streams.git] / ff.py
blobc245147b9fb6bbae163362555f2c7119d116ea77
1 #! /usr/bin/env python3
3 import os, os.path
4 import sys
5 from configparser import RawConfigParser
6 import sqlite3
7 from tempfile import NamedTemporaryFile
8 from shutil import copyfileobj
9 from contextlib import contextmanager
10 import datetime
11 import subprocess
12 import json
14 def main(profile=None, domain=None, name=None):
15 if profile is None:
16 profile = get_default_profile()
18 def key(cookie):
19 host = tuple(reversed(cookie['host'].rstrip('.').split('.')))
20 return (host, cookie['path'])
22 if domain is not None:
23 result = list()
25 with open(f'{profile}/cookies.sqlite', 'rb') as orig, \
26 TemporaryCopy(orig, prefix='cookies.', suffix='.sqlite') as copy:
27 orig.close()
28 con = sqlite3.connect(copy)
29 con.row_factory = sqlite3.Row
30 cur = con.execute('SELECT * FROM moz_cookies')
31 for row in sorted(cur, key=key):
32 assert row['inBrowserElement'] == 0
33 assert row['isSecure'] in {0, 1}
34 assert row['isHttpOnly'] in {0, 1}
35 assert row['sameSite'] in range(3)
36 if domain is not None:
37 filter_cookie(result, domain, name, row, row['name'])
38 continue
39 EPOCH = datetime.datetime(1970, 1, 1)
40 if row['expiry'] > (datetime.datetime.max - EPOCH) // datetime.timedelta(seconds=1):
41 print(end=f'exp>{datetime.MAXYEAR} ')
42 else:
43 print(end=f'exp {EPOCH + datetime.timedelta(seconds=row["expiry"])!s}')
44 print(end=f' axx {(EPOCH + datetime.timedelta(microseconds=row["lastAccessed"])).isoformat(" ", "microseconds")} cr {(EPOCH + datetime.timedelta(microseconds=row["creationTime"])).isoformat(" ", "microseconds")} ')
45 if row['isSecure']:
46 print(end='https:')
47 else:
48 print(end=' ')
49 print(end=f'//{row["host"]}{row["path"]} {row["name"]}={row["value"]}')
50 if row['isHttpOnly']:
51 print(end=' HttpOnly')
52 if row['sameSite'] or row['rawSameSite']:
53 print(end=f' SameSite={row["sameSite"]}')
54 if row['rawSameSite'] != row['sameSite']:
55 print(end=f' (raw {row["rawSameSite"]})')
56 if row['originAttributes'] > '':
57 print(end=f" originAttributes {row['originAttributes']}")
58 print()
60 try:
61 session = open(f'{profile}/sessionstore.jsonlz4', 'rb')
62 except FileNotFoundError:
63 session = open(f'{profile}/sessionstore-backups/recovery.jsonlz4',
64 'rb')
66 with session:
67 magic = session.read(8)
68 assert magic == b'mozLz40\x00'
69 uncomp = session.read(4)
70 assert len(uncomp) == 4
71 uncomp = int.from_bytes(uncomp, 'little')
72 MiB = 1 << 20
73 assert uncomp <= 8*MiB
74 session = session.read()
75 LEGACY_FRAME = 0x184C2102 . to_bytes(4, 'little')
76 session = LEGACY_FRAME + len(session).to_bytes(4, 'little') + session
77 session = subprocess.check_output(('lz4', '-d'), input=session)
78 assert len(session) == uncomp
80 for cookie in sorted(json.loads(session)['cookies'], key=key):
81 partitionKey = cookie['originAttributes'].pop('partitionKey', '')
82 assert cookie['originAttributes'] == {
83 'firstPartyDomain': '',
84 'geckoViewSessionContextId': '',
85 'inIsolatedMozBrowser': False,
86 'privateBrowsingId': 0,
87 'userContextId': 0,
89 assert cookie.get('secure', True) is True
90 assert cookie.get('httponly', True) is True
91 assert cookie.get('sameSite', 1) in {1, 2}
92 found_name = cookie.get("name", "")
93 if domain is not None:
94 filter_cookie(result, domain, name, cookie, found_name)
95 continue
96 if 'secure' in cookie:
97 print(end='https:')
98 else:
99 print(end=' ')
100 print(end=f'//{cookie["host"]}{cookie["path"]} {found_name}={cookie["value"]}')
101 if 'httponly' in cookie:
102 print(end=' httponly')
103 if 'sameSite' in cookie:
104 print(end=f' sameSite={cookie["sameSite"]}')
105 if partitionKey > '':
106 print(end=f' partitionKey={partitionKey}')
107 print()
109 if domain is not None:
110 if name is not None:
111 if not result:
112 raise LookupError(f'Cookie {name!r} not found')
113 print(' '.join(result))
115 def filter_cookie(result, domain, name, cookie, found_name):
116 if cookie['host'].startswith('.'):
117 if not f'.{domain.lower()}'.endswith(cookie['host']):
118 return
119 else:
120 if cookie['host'] != domain.lower():
121 return
122 if name is None:
123 result.append(f'{found_name}={cookie["value"]}')
124 elif name == found_name:
125 if result:
126 raise LookupError('Multiple matching entries')
127 result.append(cookie['value'])
129 def get_default_profile():
130 if sys.platform == 'win32':
131 dir = f"{os.environ['APPDATA']}\\Mozilla\\Firefox"
132 else:
133 dir = f"{os.environ['HOME']}/.mozilla/firefox"
135 parser = RawConfigParser(delimiters=('=',), comment_prefixes=())
136 with open(f'{dir}/profiles.ini', 'rt', encoding='ascii') as file:
137 parser.read_file(file)
139 profile = None
140 profiles = 0
141 for section in parser.sections():
142 if section.startswith('Install'):
143 assert profile is None
144 profile = parser.get(section, 'Default')
145 profiles += section.startswith('Profile')
146 if profile is not None:
147 return os.path.join(dir, profile)
149 default = None
150 for i in range(profiles):
151 if parser.has_option(f'Profile{i}', 'Default'):
152 assert parser.get(f'Profile{i}', 'Default') == '1'
153 assert not default
154 default = i
156 if parser.get(f'Profile{default}', 'IsRelative') == '1':
157 return os.path.join(dir, parser.get(f'Profile{default}', 'Path'))
158 else:
159 assert parser.get(f'Profile{default}', 'IsRelative') == '0'
160 return parser.get(f'Profile{default}', 'Path')
162 @contextmanager
163 def TemporaryCopy(orig, *pos, **kw):
164 if sys.platform == 'win32':
165 with NamedTemporaryFile(*pos, **kw, delete=False) as temp:
166 copyfileobj(orig, temp)
167 temp = temp.name
168 try:
169 yield temp
170 finally:
171 os.unlink(temp)
172 else:
173 with NamedTemporaryFile(*pos, **kw) as temp:
174 copyfileobj(orig, temp)
175 yield temp.name
177 if __name__ == '__main__':
178 main(*sys.argv[1:])