Skip to content

Commit

Permalink
Add browser-cookie CLI
Browse files Browse the repository at this point in the history
Also:

- Clean up/clarify BrowserCookieError() messages a bit
- Make FirefoxBased and Safari classes (and functions derived therefrom)
  take a key_file parameter, but ignore it, for a consistent interface
  with Chromium-based browsers
- Cleanup all trailing whitespace (with https://github.com/dlenski/wtf)
  • Loading branch information
dlenski committed Dec 14, 2023
1 parent 53fa5e3 commit 4da9f1d
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 20 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This is a python3 fork of [Richard Penman's Browser Cookie](https://github.com/r
pip install browser-cookie3
```

## Usage
## Python usage

Here is a *dangerous* hack to extract the title from a webpage:
```python
Expand Down Expand Up @@ -86,6 +86,25 @@ Alternatively if you are only interested in cookies from a specific domain, you
>>> get_title(r.content)
'richardpenman / home — Bitbucket'
```

## Command-line usage

Run `browser-cookie --help` for all options. Brief examples:

```sh
$ browser-cookie --firefox stackoverflow.com acct
t=BASE64_STRING_DESCRIBING_YOUR_STACKOVERFLOW_ACCOUNT

$ browser-cookie --json --chrome stackoverflow.com acct
{"version": 0, "name": "acct", "value": "t=BASE64_STRING_DESCRIBING_YOUR_STACKOVERFLOW_ACCOUNT",
"port_specified": false, "domain": ".stackoverflow.com", "domain_specified": true,
"domain_initial_dot": true, "path": "/", "path_specified": true, "secure": 1,
"expires": 1657049738, "discard": false, "rfc2109": false}

$ browser-cookie nonexistent-domain.com nonexistent-cookie && echo "Cookie found" || echo "No cookie found"
No cookie found
```

## Fresh cookie files
Creating and testing a fresh cookie file can help eliminate some possible user specific issues. It also allows you to upload a cookie file you are having issues with, since you should never upload your main cookie file!
### Chrome and chromium
Expand Down
54 changes: 35 additions & 19 deletions browser_cookie3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ def __add_key_and_cookie_file(self,

if not cookie_file:
raise BrowserCookieError(
'Failed to find {} cookie'.format(self.browser))
'Failed to find cookies for {} browser'.format(self.browser))

self.cookie_file = cookie_file

Expand All @@ -494,9 +494,14 @@ def load(self):
cur.execute('SELECT host_key, path, secure, expires_utc, name, value, encrypted_value, is_httponly '
'FROM cookies WHERE host_key like ?;', ('%{}%'.format(self.domain_name),))
except sqlite3.OperationalError:
# chrome >=56
cur.execute('SELECT host_key, path, is_secure, expires_utc, name, value, encrypted_value, is_httponly '
'FROM cookies WHERE host_key like ?;', ('%{}%'.format(self.domain_name),))
try:
# chrome >=56
cur.execute('SELECT host_key, path, is_secure, expires_utc, name, value, encrypted_value, is_httponly '
'FROM cookies WHERE host_key like ?;', ('%{}%'.format(self.domain_name),))
except sqlite3.OperationalError as e:
if e.args[0].startswith(('no such table: ', 'file is not a database')):
raise BrowserCookieError('File {} is not a Chromium-based browser cookie file'.format(self.tmp_cookie_file))


for item in cur.fetchall():
# Per https://github.com/chromium/chromium/blob/main/base/time/time.h#L5-L7,
Expand Down Expand Up @@ -845,7 +850,7 @@ def __init__(self, cookie_file=None, domain_name="", key_file=None):
class FirefoxBased:
"""Superclass for Firefox based browsers"""

def __init__(self, browser_name, cookie_file=None, domain_name="", **kwargs):
def __init__(self, browser_name, cookie_file=None, domain_name="", key_file=None, **kwargs):
self.browser_name = browser_name
self.cookie_file = cookie_file or self.__find_cookie_file(**kwargs)
# current sessions are saved in sessionstore.js
Expand Down Expand Up @@ -968,8 +973,13 @@ def load(self):
# firefoxbased seems faster with legacy mode
with _DatabaseConnetion(self.cookie_file, True) as con:
cur = con.cursor()
cur.execute('select host, path, isSecure, expiry, name, value, isHttpOnly from moz_cookies '
'where host like ?', ('%{}%'.format(self.domain_name),))
try:
cur.execute('select host, path, isSecure, expiry, name, value, isHttpOnly from moz_cookies '
'where host like ?', ('%{}%'.format(self.domain_name),))
except sqlite3.DatabaseError as e:
if e.args[0].startswith(('no such table: ', 'file is not a database')):
raise BrowserCookieError('File {} is not a Firefox cookie file'.format(self.tmp_cookie_file))
raise

for item in cur.fetchall():
host, path, secure, expires, name, value, http_only = item
Expand All @@ -986,7 +996,7 @@ def load(self):
class Firefox(FirefoxBased):
"""Class for Firefox"""

def __init__(self, cookie_file=None, domain_name=""):
def __init__(self, cookie_file=None, domain_name="", key_file=None):
args = {
'linux_data_dirs': [
'~/snap/firefox/common/.mozilla/firefox',
Expand All @@ -1000,13 +1010,13 @@ def __init__(self, cookie_file=None, domain_name=""):
'~/Library/Application Support/Firefox'
]
}
super().__init__('Firefox', cookie_file, domain_name, **args)
super().__init__('Firefox', cookie_file, domain_name, key_file, **args)


class LibreWolf(FirefoxBased):
"""Class for LibreWolf"""

def __init__(self, cookie_file=None, domain_name=""):
def __init__(self, cookie_file=None, domain_name="", key_file=None):
args = {
'linux_data_dirs': [
'~/snap/librewolf/common/.librewolf',
Expand All @@ -1020,7 +1030,7 @@ def __init__(self, cookie_file=None, domain_name=""):
'~/Library/Application Support/librewolf'
]
}
super().__init__('LibreWolf', cookie_file, domain_name, **args)
super().__init__('LibreWolf', cookie_file, domain_name, key_file, **args)


class Safari:
Expand All @@ -1034,7 +1044,7 @@ class Safari:
'~/Library/Cookies/Cookies.binarycookies'
]

def __init__(self, cookie_file=None, domain_name="") -> None:
def __init__(self, cookie_file=None, domain_name="", key_file=None) -> None:
self.__offset = 0
self.__domain_name = domain_name
self.__buffer = None
Expand Down Expand Up @@ -1203,33 +1213,36 @@ def vivaldi(cookie_file=None, domain_name="", key_file=None):
return Vivaldi(cookie_file, domain_name, key_file).load()


def firefox(cookie_file=None, domain_name=""):
def firefox(cookie_file=None, domain_name="", key_file=None):
"""Returns a cookiejar of the cookies and sessions used by Firefox. Optionally
pass in a domain name to only load cookies from the specified domain
"""
return Firefox(cookie_file, domain_name).load()
return Firefox(cookie_file, domain_name, key_file).load()


def librewolf(cookie_file=None, domain_name=""):
def librewolf(cookie_file=None, domain_name="", key_file=None):
"""Returns a cookiejar of the cookies and sessions used by LibreWolf. Optionally
pass in a domain name to only load cookies from the specified domain
"""
return LibreWolf(cookie_file, domain_name).load()
return LibreWolf(cookie_file, domain_name, key_file).load()


def safari(cookie_file=None, domain_name=""):
def safari(cookie_file=None, domain_name="", key_file=None):
"""Returns a cookiejar of the cookies and sessions used by Safari. Optionally
pass in a domain name to only load cookies from the specified domain
"""
return Safari(cookie_file, domain_name).load()
return Safari(cookie_file, domain_name, key_file).load()


all_browsers = [chrome, chromium, opera, opera_gx, brave, edge, vivaldi, firefox, librewolf, safari]


def load(domain_name=""):
"""Try to load cookies from all supported browsers and return combined cookiejar
Optionally pass in a domain name to only load cookies from the specified domain
"""
cj = http.cookiejar.CookieJar()
for cookie_fn in [chrome, chromium, opera, opera_gx, brave, edge, vivaldi, firefox, librewolf, safari]:
for cookie_fn in all_browsers:
try:
for cookie in cookie_fn(domain_name=domain_name):
cj.set_cookie(cookie)
Expand All @@ -1238,5 +1251,8 @@ def load(domain_name=""):
return cj


__all__ = ['BrowserCookieError', 'load', 'all_browsers'] + all_browsers


if __name__ == '__main__':
print(load())
62 changes: 62 additions & 0 deletions browser_cookie3/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-

import argparse
import browser_cookie3
import json


def parse_args(args=None):
p = argparse.ArgumentParser(
description='Extract browser cookies using browser_cookie3.',
epilog='Exit status is 0 if cookie was found, 1 if not found, and 2 if errors occurred',
)
p.add_argument('-j', '--json', action='store_true',
help="Output JSON with all cookie details, rather than just the cookie's value")
p.add_argument('domain')
p.add_argument('name')

g = p.add_argument_group('Browser selection')
x = g.add_mutually_exclusive_group()
x.add_argument('-a', '--all', dest='browser', action='store_const', const=None, default=None,
help="Try to load cookies from all supported browsers")
for browser in browser_cookie3.all_browsers:
x.add_argument('--' + browser.__name__, dest='browser', action='store_const', const=browser,
help="Load cookies from {} browser".format(browser.__name__.title()))
g.add_argument('-f', '--cookie-file',
help="Use specific cookie file (default is to autodetect).")
g.add_argument('-k', '--key-file',
help="Use specific key file (default is to autodetect).")

args = p.parse_args(args)

if not args.browser and (args.cookie_file or args.key_file):
p.error("Must specify a specific browser with --cookie-file or --key-file arguments")

return p, args


def main(args=None):
p, args = parse_args(args)

try:
if args.browser:
cj = args.browser(cookie_file=args.cookie_file, key_file=args.key_file)
else:
cj = browser_cookie3.load()
except browser_cookie3.BrowserCookieError as e:
p.error(e.args[0])

for cookie in cj:
if cookie.domain in (args.domain, '.' + args.domain) and cookie.name == args.name:
if not args.json:
print(cookie.value)
else:
print(json.dumps({k: v for k, v in vars(cookie).items()
if v is not None and (k, v) != ('_rest', {})}))
break
else:
raise SystemExit(1)


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
'dbus-python; python_version < "3.7" and ("bsd" in sys_platform or sys_platform == "linux")',
'jeepney; python_version >= "3.7" and ("bsd" in sys_platform or sys_platform == "linux")'
],
entry_points={'console_scripts': ['browser-cookie=browser_cookie3.__main__:main']},
license='lgpl'
)

0 comments on commit 4da9f1d

Please sign in to comment.