binarycookies 2.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ MIT License
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
16
+ SOFTWARE.
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.1
2
+ Name: binarycookies
3
+ Version: 2.1.4
4
+ Summary: Python Binary Cookies (de)serializer
5
+ Author: Daniel Tom
6
+ Author-email: d.e.tom89@gmail.com
7
+ Requires-Python: >=3.8,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Requires-Dist: pydantic (>=2.0.0,<3.0.0)
14
+ Requires-Dist: typer (>=0.12.3,<0.17.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ [![Github Actions Status](https://github.com/dan1elt0m/binary-cookies-reader/workflows/test/badge.svg)](https://github.com/dan1elt0m/binary-cookies-reader/actions/workflows/test.yml)
18
+
19
+ # Binary Cookies
20
+
21
+ Python library and CLI tool for reading and writing binary cookies files.
22
+
23
+ ## Requirements
24
+
25
+ - Python 3.9 or higher
26
+
27
+ ## Installation
28
+ ```bash
29
+ pip install binarycookies
30
+ ```
31
+ If you want to use the parser as CLI, it's recommended to use pipx to install the package in an isolated environment.
32
+ ```bash
33
+ pipx install binarycookies
34
+ ```
35
+
36
+ ## Basic Usage CLI
37
+ After installation, you can use the command-line interface to read a binary cookies file:
38
+
39
+ ```bash
40
+ bcparser <path_to_binary_cookies_file>
41
+ ```
42
+ Replace <path_to_binary_cookies_file> with the path to the binary cookie file you want to read.
43
+
44
+ ### Basic Usage Python
45
+
46
+ #### Deserialization
47
+
48
+ ```python
49
+ import binarycookies
50
+
51
+ with open("path/to/cookies.binarycookies", "rb") as f:
52
+ cookies = binarycookies.load(f)
53
+ ```
54
+
55
+ #### Serialization
56
+
57
+ ```python
58
+ import binarycookies
59
+
60
+ cookie = {
61
+ "name": "session_id",
62
+ "value": "abc123",
63
+ "url": "https://example.com",
64
+ "path": "/",
65
+ "create_datetime": "2023-10-01T12:34:56+00:00",
66
+ "expiry_datetime": "2023-12-31T23:59:59+00:00",
67
+ "flag": "Secure"
68
+ }
69
+
70
+ with open("path/to/cookies.binarycookies", "wb") as f:
71
+ binarycookies.dump(cookie, f)
72
+ ```
73
+
74
+ ## Output Types
75
+
76
+ The `bcparser` CLI supports two output types: `json` (default) and `ascii`.
77
+
78
+ ### JSON Output
79
+
80
+ The `json` output type formats the cookies as a JSON array, making it easy to parse and manipulate programmatically.
81
+
82
+ Example usage:
83
+ ```sh
84
+ bcparser path/to/cookies.binarycookies --output json
85
+ ```
86
+
87
+ Example output JSON:
88
+ ```json
89
+ [
90
+ {
91
+ "name": "session_id",
92
+ "value": "abc123",
93
+ "url": "https://example.com",
94
+ "path": "/",
95
+ "create_datetime": "2023-10-01T12:34:56+00:00",
96
+ "expiry_datetime": "2023-12-31T23:59:59+00:00",
97
+ "flag": "Secure"
98
+ },
99
+ {
100
+ "name": "user_token",
101
+ "value": "xyz789",
102
+ "url": "https://example.com",
103
+ "path": "/account",
104
+ "create_datetime": "2023-10-01T12:34:56+00:00",
105
+ "expiry_datetime": "2023-12-31T23:59:59+00:00",
106
+ "flag": "HttpOnly"
107
+ }
108
+ ]
109
+ ```
110
+
111
+ ### ASCII Output
112
+ The ascii output type formats the cookies in a simple, line-by-line text format, making it easy to read and pipe to other command-line tools.
113
+
114
+ Example usage:
115
+ ```sh
116
+ bcparser path/to/cookies.binarycookies --output ascii
117
+ ```
118
+
119
+ Example output ASCII:
120
+ ```text
121
+ Name: session_id
122
+ Value: abc123
123
+ URL: https://example.com
124
+ Path: /
125
+ Created: 2023-10-01T12:34:56+00:00
126
+ Expires: 2023-12-31T23:59:59+00:00
127
+ Flag: Secure
128
+ ----------------------------------------
129
+ Name: user_token
130
+ Value: xyz789
131
+ URL: https://example.com
132
+ Path: /account
133
+ Created: 2023-10-01T12:34:56+00:00
134
+ Expires: 2023-12-31T23:59:59+00:00
135
+ Flag: HttpOnly
136
+ ----------------------------------------
137
+ ```
138
+
139
+ ### License
140
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
141
+
142
+ ### Contributing
143
+ Contributions are welcome! If you find a bug or have a feature request, please open an issue on GitHub. Pull requests are also welcome.
@@ -0,0 +1,127 @@
1
+ [![Github Actions Status](https://github.com/dan1elt0m/binary-cookies-reader/workflows/test/badge.svg)](https://github.com/dan1elt0m/binary-cookies-reader/actions/workflows/test.yml)
2
+
3
+ # Binary Cookies
4
+
5
+ Python library and CLI tool for reading and writing binary cookies files.
6
+
7
+ ## Requirements
8
+
9
+ - Python 3.9 or higher
10
+
11
+ ## Installation
12
+ ```bash
13
+ pip install binarycookies
14
+ ```
15
+ If you want to use the parser as CLI, it's recommended to use pipx to install the package in an isolated environment.
16
+ ```bash
17
+ pipx install binarycookies
18
+ ```
19
+
20
+ ## Basic Usage CLI
21
+ After installation, you can use the command-line interface to read a binary cookies file:
22
+
23
+ ```bash
24
+ bcparser <path_to_binary_cookies_file>
25
+ ```
26
+ Replace <path_to_binary_cookies_file> with the path to the binary cookie file you want to read.
27
+
28
+ ### Basic Usage Python
29
+
30
+ #### Deserialization
31
+
32
+ ```python
33
+ import binarycookies
34
+
35
+ with open("path/to/cookies.binarycookies", "rb") as f:
36
+ cookies = binarycookies.load(f)
37
+ ```
38
+
39
+ #### Serialization
40
+
41
+ ```python
42
+ import binarycookies
43
+
44
+ cookie = {
45
+ "name": "session_id",
46
+ "value": "abc123",
47
+ "url": "https://example.com",
48
+ "path": "/",
49
+ "create_datetime": "2023-10-01T12:34:56+00:00",
50
+ "expiry_datetime": "2023-12-31T23:59:59+00:00",
51
+ "flag": "Secure"
52
+ }
53
+
54
+ with open("path/to/cookies.binarycookies", "wb") as f:
55
+ binarycookies.dump(cookie, f)
56
+ ```
57
+
58
+ ## Output Types
59
+
60
+ The `bcparser` CLI supports two output types: `json` (default) and `ascii`.
61
+
62
+ ### JSON Output
63
+
64
+ The `json` output type formats the cookies as a JSON array, making it easy to parse and manipulate programmatically.
65
+
66
+ Example usage:
67
+ ```sh
68
+ bcparser path/to/cookies.binarycookies --output json
69
+ ```
70
+
71
+ Example output JSON:
72
+ ```json
73
+ [
74
+ {
75
+ "name": "session_id",
76
+ "value": "abc123",
77
+ "url": "https://example.com",
78
+ "path": "/",
79
+ "create_datetime": "2023-10-01T12:34:56+00:00",
80
+ "expiry_datetime": "2023-12-31T23:59:59+00:00",
81
+ "flag": "Secure"
82
+ },
83
+ {
84
+ "name": "user_token",
85
+ "value": "xyz789",
86
+ "url": "https://example.com",
87
+ "path": "/account",
88
+ "create_datetime": "2023-10-01T12:34:56+00:00",
89
+ "expiry_datetime": "2023-12-31T23:59:59+00:00",
90
+ "flag": "HttpOnly"
91
+ }
92
+ ]
93
+ ```
94
+
95
+ ### ASCII Output
96
+ The ascii output type formats the cookies in a simple, line-by-line text format, making it easy to read and pipe to other command-line tools.
97
+
98
+ Example usage:
99
+ ```sh
100
+ bcparser path/to/cookies.binarycookies --output ascii
101
+ ```
102
+
103
+ Example output ASCII:
104
+ ```text
105
+ Name: session_id
106
+ Value: abc123
107
+ URL: https://example.com
108
+ Path: /
109
+ Created: 2023-10-01T12:34:56+00:00
110
+ Expires: 2023-12-31T23:59:59+00:00
111
+ Flag: Secure
112
+ ----------------------------------------
113
+ Name: user_token
114
+ Value: xyz789
115
+ URL: https://example.com
116
+ Path: /account
117
+ Created: 2023-10-01T12:34:56+00:00
118
+ Expires: 2023-12-31T23:59:59+00:00
119
+ Flag: HttpOnly
120
+ ----------------------------------------
121
+ ```
122
+
123
+ ### License
124
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
125
+
126
+ ### Contributing
127
+ Contributions are welcome! If you find a bug or have a feature request, please open an issue on GitHub. Pull requests are also welcome.
@@ -0,0 +1,111 @@
1
+ [tool.poetry]
2
+ name = "binarycookies"
3
+ version = "2.1.4"
4
+ description = "Python Binary Cookies (de)serializer"
5
+ authors = ["Daniel Tom <d.e.tom89@gmail.com>"]
6
+ readme = "README.md"
7
+ packages = [{include = "binarycookies", from="src"}]
8
+
9
+ [tool.poetry.dependencies]
10
+ python = ">=3.8,<4.0"
11
+ typer = ">=0.12.3,<0.17.0"
12
+ pydantic = ">=2.0.0,<3.0.0"
13
+
14
+ [tool.poetry.scripts]
15
+ bcparser = "binarycookies.__main__:main"
16
+
17
+ [tool.poetry.group.dev.dependencies]
18
+ pytest = "^8.2.2"
19
+ ruff = ">=0.5.1,<0.8.0"
20
+ pytest-cov = "^5.0.0"
21
+
22
+ [build-system]
23
+ requires = ["poetry-core"]
24
+ build-backend = "poetry.core.masonry.api"
25
+
26
+
27
+ [tool.ruff]
28
+ # https://beta.ruff.rs/docs/rules/
29
+ # https://beta.ruff.rs/docs/settings/
30
+ lint.ignore = [
31
+ "FBT003",
32
+ "ANN101",
33
+ "DTZ007",
34
+ "TRY003",
35
+ "A003", # shadow built-in types
36
+ "ANN002", # type annotation for **kwargs
37
+ "ANN003", # type annotation for *args
38
+ "ANN102", # Missing type annotation for `cls` in classmethod
39
+ "D101", # docstring in public classes
40
+ "D102", # docstring in public methods
41
+ "D103", # docstring in public functions
42
+ "DTZ", # timezone information in datetimes
43
+ "RUF012", # causes failures in pydantic models
44
+ "PLR0911", # for some reason can't ignore this on func definition
45
+ "ISC001",
46
+ "COM812"
47
+ ]
48
+ lint.extend-select = [
49
+ "I",
50
+ "N",
51
+ "ASYNC",
52
+ "ANN",
53
+ "BLE",
54
+ "FBT",
55
+ "A",
56
+ "COM",
57
+ "C4",
58
+ "DTZ",
59
+ "ISC",
60
+ "ICN",
61
+ "T20",
62
+ "D101",
63
+ "D102",
64
+ "D103",
65
+ "D419",
66
+ "PT",
67
+ "Q",
68
+ "RSE",
69
+ "RET",
70
+ "SLF",
71
+ "SLOT",
72
+ "SIM",
73
+ "TID252",
74
+ "ARG",
75
+ "ERA001",
76
+ "G010",
77
+ "PGH005",
78
+ "PL",
79
+ "TRY",
80
+ "FLY",
81
+ "NPY",
82
+ "AIR",
83
+ "PERF101",
84
+ "PERF102",
85
+ "RUF",
86
+ ]
87
+ line-length = 120
88
+ target-version = "py310"
89
+ exclude = ["deploy", ".venv"]
90
+
91
+ [tool.ruff.lint.per-file-ignores]
92
+ "tests/*" = [
93
+ "ANN001",
94
+ "ARG001",
95
+ "D101",
96
+ "D102",
97
+ "D103",
98
+ "D419",
99
+ "DTZ005",
100
+ "DTZ011",
101
+ "PLR2004",
102
+ "SLF001",
103
+ "PLR0913",
104
+ "PGH005",
105
+ ]
106
+
107
+ [tool.ruff.lint.pylint]
108
+ max-args = 7
109
+
110
+ [tool.ruff.lint.flake8-annotations]
111
+ suppress-none-returning = true
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+ from setuptools import setup
3
+
4
+ package_dir = \
5
+ {'': 'src'}
6
+
7
+ packages = \
8
+ ['binarycookies']
9
+
10
+ package_data = \
11
+ {'': ['*']}
12
+
13
+ install_requires = \
14
+ ['pydantic>=2.0.0,<3.0.0', 'typer>=0.12.3,<0.17.0']
15
+
16
+ entry_points = \
17
+ {'console_scripts': ['bcparser = binarycookies.__main__:main']}
18
+
19
+ setup_kwargs = {
20
+ 'name': 'binarycookies',
21
+ 'version': '2.1.4',
22
+ 'description': 'Python Binary Cookies (de)serializer',
23
+ 'long_description': '[![Github Actions Status](https://github.com/dan1elt0m/binary-cookies-reader/workflows/test/badge.svg)](https://github.com/dan1elt0m/binary-cookies-reader/actions/workflows/test.yml)\n\n# Binary Cookies\n\nPython library and CLI tool for reading and writing binary cookies files.\n\n## Requirements\n\n- Python 3.9 or higher\n\n## Installation\n```bash \npip install binarycookies\n```\nIf you want to use the parser as CLI, it\'s recommended to use pipx to install the package in an isolated environment.\n```bash \npipx install binarycookies\n```\n\n## Basic Usage CLI\nAfter installation, you can use the command-line interface to read a binary cookies file:\n\n```bash\nbcparser <path_to_binary_cookies_file>\n```\nReplace <path_to_binary_cookies_file> with the path to the binary cookie file you want to read.\n\n### Basic Usage Python\n\n#### Deserialization\n\n```python\nimport binarycookies \n\nwith open("path/to/cookies.binarycookies", "rb") as f:\n cookies = binarycookies.load(f)\n```\n\n#### Serialization\n\n```python\nimport binarycookies \n\ncookie = {\n "name": "session_id",\n "value": "abc123",\n "url": "https://example.com",\n "path": "/",\n "create_datetime": "2023-10-01T12:34:56+00:00",\n "expiry_datetime": "2023-12-31T23:59:59+00:00",\n "flag": "Secure"\n}\n\nwith open("path/to/cookies.binarycookies", "wb") as f:\n binarycookies.dump(cookie, f)\n```\n\n## Output Types\n\nThe `bcparser` CLI supports two output types: `json` (default) and `ascii`.\n\n### JSON Output\n\nThe `json` output type formats the cookies as a JSON array, making it easy to parse and manipulate programmatically.\n\nExample usage:\n```sh\nbcparser path/to/cookies.binarycookies --output json\n```\n\nExample output JSON:\n```json\n[\n {\n "name": "session_id",\n "value": "abc123",\n "url": "https://example.com",\n "path": "/",\n "create_datetime": "2023-10-01T12:34:56+00:00",\n "expiry_datetime": "2023-12-31T23:59:59+00:00",\n "flag": "Secure"\n },\n {\n "name": "user_token",\n "value": "xyz789",\n "url": "https://example.com",\n "path": "/account",\n "create_datetime": "2023-10-01T12:34:56+00:00",\n "expiry_datetime": "2023-12-31T23:59:59+00:00",\n "flag": "HttpOnly"\n }\n]\n```\n\n### ASCII Output\nThe ascii output type formats the cookies in a simple, line-by-line text format, making it easy to read and pipe to other command-line tools.\n\nExample usage:\n```sh\nbcparser path/to/cookies.binarycookies --output ascii\n```\n\nExample output ASCII:\n```text\nName: session_id\nValue: abc123\nURL: https://example.com\nPath: /\nCreated: 2023-10-01T12:34:56+00:00\nExpires: 2023-12-31T23:59:59+00:00\nFlag: Secure\n----------------------------------------\nName: user_token\nValue: xyz789\nURL: https://example.com\nPath: /account\nCreated: 2023-10-01T12:34:56+00:00\nExpires: 2023-12-31T23:59:59+00:00\nFlag: HttpOnly\n----------------------------------------\n```\n\n### License\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n\n### Contributing\nContributions are welcome! If you find a bug or have a feature request, please open an issue on GitHub. Pull requests are also welcome.',
24
+ 'author': 'Daniel Tom',
25
+ 'author_email': 'd.e.tom89@gmail.com',
26
+ 'maintainer': 'None',
27
+ 'maintainer_email': 'None',
28
+ 'url': 'None',
29
+ 'package_dir': package_dir,
30
+ 'packages': packages,
31
+ 'package_data': package_data,
32
+ 'install_requires': install_requires,
33
+ 'entry_points': entry_points,
34
+ 'python_requires': '>=3.8,<4.0',
35
+ }
36
+
37
+
38
+ setup(**setup_kwargs)
@@ -0,0 +1,4 @@
1
+ from binarycookies._deserialize import load, loads
2
+ from binarycookies._serialize import dump, dumps
3
+
4
+ __all__ = ["dump", "dumps", "load", "loads"]
@@ -0,0 +1,48 @@
1
+ import json
2
+ from datetime import datetime
3
+ from enum import Enum
4
+ from typing import Type
5
+
6
+ import typer
7
+ from rich import print
8
+
9
+ from binarycookies import load
10
+
11
+
12
+ class DateTimeEncoder(json.JSONEncoder):
13
+ def default(self, obj: Type) -> str:
14
+ if isinstance(obj, datetime):
15
+ return obj.isoformat()
16
+ return super().default(obj)
17
+
18
+
19
+ class OutputType(str, Enum):
20
+ json = "json"
21
+ ascii = "ascii"
22
+
23
+
24
+ def cli(file_path: str, output: str = "json"):
25
+ """CLI entrypoint for reading Binary Cookies"""
26
+ with open(file_path, "rb") as f:
27
+ cookies = load(f)
28
+ if output == OutputType.json:
29
+ print(json.dumps([cookie.model_dump() for cookie in cookies], indent=2, cls=DateTimeEncoder))
30
+ elif output == OutputType.ascii:
31
+ for cookie in cookies:
32
+ print(f"Name: {cookie.name}")
33
+ print(f"Value: {cookie.value}")
34
+ print(f"URL: {cookie.url}")
35
+ print(f"Path: {cookie.path}")
36
+ print(f"Created: {cookie.create_datetime.isoformat()}")
37
+ print(f"Expires: {cookie.expiry_datetime.isoformat()}")
38
+ print(f"Flag: {cookie.flag.value}")
39
+ print("-" * 40)
40
+
41
+
42
+ def main():
43
+ """CLI entrypoint for reading Binary Cookies"""
44
+ typer.run(cli)
45
+
46
+
47
+ if __name__ == "__main__":
48
+ main()
@@ -0,0 +1,155 @@
1
+ from datetime import datetime, timezone
2
+ from io import BytesIO
3
+ from struct import unpack
4
+ from typing import BinaryIO, List, Union
5
+
6
+ from binarycookies.models import (
7
+ BcField,
8
+ BinaryCookiesDecodeError,
9
+ Cookie,
10
+ CookieFields,
11
+ FileFields,
12
+ Flag,
13
+ Format,
14
+ )
15
+
16
+ FLAGS = {
17
+ 0: Flag.UNKNOWN,
18
+ 1: Flag.SECURE,
19
+ 4: Flag.HTTPONLY,
20
+ 5: Flag.SECURE_HTTPONLY,
21
+ }
22
+
23
+
24
+ def interpret_flag(flags: int) -> Flag:
25
+ """Interprets the flags of a cookie and returns a human-readable string."""
26
+ return FLAGS.get(flags, Flag.UNKNOWN)
27
+
28
+
29
+ def mac_epoch_to_date(epoch: int) -> datetime:
30
+ """Converts a mac epoch time to a datetime object."""
31
+ return datetime.fromtimestamp(epoch + 978307200, tz=timezone.utc)
32
+
33
+
34
+ def read_string(data: BytesIO, size: int) -> str:
35
+ """Reads a string from binary file."""
36
+ result = ""
37
+ count = 0
38
+ c = data.read(1)
39
+ while unpack("<b", c)[0] != 0:
40
+ count += 1
41
+ if count > size:
42
+ break
43
+ result += str(c.decode())
44
+ c = data.read(1)
45
+ return result
46
+
47
+
48
+ def read_field(data: BytesIO, field: BcField) -> Union[str, int]:
49
+ """Reads a field from binary data."""
50
+ data.seek(field.offset)
51
+ if field.format == Format.string:
52
+ return read_string(data, field.size)
53
+ return unpack(field.format, data.read(field.size))[0]
54
+
55
+
56
+ def read_cookie(cookie: BytesIO, cookie_size: int) -> Cookie:
57
+ """Reads a cookie from the given offset in the page."""
58
+
59
+ cookie_fields = CookieFields()
60
+ flag_int = read_field(cookie, cookie_fields.flag)
61
+ flag = interpret_flag(flag_int)
62
+
63
+ url_offset = read_field(cookie, cookie_fields.url_offset)
64
+ name_offset = read_field(cookie, cookie_fields.name_offset)
65
+ path_offset = read_field(cookie, cookie_fields.path_offset)
66
+ value_offset = read_field(cookie, cookie_fields.value_offset)
67
+
68
+ expiry_datetime = mac_epoch_to_date(read_field(cookie, cookie_fields.expiry_date))
69
+ create_datetime = mac_epoch_to_date(read_field(cookie, cookie_fields.create_date))
70
+
71
+ url = read_field(cookie, BcField(offset=url_offset, size=name_offset - url_offset, format=Format.string))
72
+ name = read_field(cookie, BcField(offset=name_offset, size=path_offset - name_offset, format=Format.string))
73
+ path = read_field(cookie, BcField(offset=path_offset, size=value_offset - path_offset, format=Format.string))
74
+ value = read_field(cookie, BcField(offset=value_offset, size=cookie_size - value_offset, format=Format.string))
75
+
76
+ return Cookie(
77
+ name=name,
78
+ value=value,
79
+ url=url,
80
+ path=path,
81
+ create_datetime=create_datetime,
82
+ expiry_datetime=expiry_datetime,
83
+ flag=flag,
84
+ )
85
+
86
+
87
+ def get_cookie_offsets(page: BytesIO, num_cookies: int) -> List[int]:
88
+ """Reads the offsets of the cookies in the page."""
89
+ return [read_field(page, BcField(offset=8 + (4 * i), size=4, format=Format.integer)) for i in range(num_cookies)]
90
+
91
+
92
+ def get_file_pages(binary_file: BytesIO, num_pages: int) -> List[int]:
93
+ """Reads the sizes of the pages in the binary file."""
94
+ return [
95
+ read_field(binary_file, BcField(offset=8 + (i * 4), size=4, format=Format.integer_be)) for i in range(num_pages)
96
+ ]
97
+
98
+
99
+ def _deserialize_page(page: BytesIO) -> List[Cookie]:
100
+ """Reads a binary cookie file and returns a list of cookies."""
101
+ num_cookies = read_field(page, BcField(offset=4, size=4, format=Format.integer))
102
+ cookie_offsets = get_cookie_offsets(page, num_cookies)
103
+ cookies = []
104
+ for offset in cookie_offsets:
105
+ cookie_size = read_field(page, BcField(offset=offset, size=4, format=Format.integer))
106
+ page.seek(offset)
107
+ cookie = page.read(cookie_size)
108
+ cookies.append(read_cookie(BytesIO(cookie), cookie_size))
109
+ return cookies
110
+
111
+
112
+ def load(bf: BinaryIO) -> List[Cookie]:
113
+ """Deserializes a binary cookie file and returns a list of Cookie objects.
114
+ Args:
115
+ bf (BinaryIO): A binary file object containing the binary cookie data."""
116
+ # Check if the file is empty
117
+ if bf.readable() and bf.read(1) == b"":
118
+ raise BinaryCookiesDecodeError("The file is empty.")
119
+ # Reset the file pointer to the beginning
120
+ bf.seek(0)
121
+ # Check if the file is a valid binary cookies file
122
+ if bf.readable() and bf.read(4) != b"cook":
123
+ raise BinaryCookiesDecodeError("The file is not a valid binary cookies file. Missing magic String:cook.")
124
+ # Reset the file pointer to the beginning
125
+ bf.seek(0)
126
+ # Deserialize the binary cookies file
127
+ return loads(BytesIO(bf.read()))
128
+
129
+
130
+ def loads(b: BytesIO) -> List[Cookie]:
131
+ """Deserializes a binary cookie file and returns a list of Cookie objects.
132
+
133
+ Args:
134
+ b (BytesIO): A BytesIO object containing the binary cookie data.
135
+ Returns:
136
+ List[Cookie]: A list of Cookie objects.
137
+ """
138
+ all_cookies = []
139
+ file_fields = FileFields()
140
+
141
+ # Number of pages in the binary file: 4 bytes
142
+ num_pages = read_field(b, field=file_fields.num_pages)
143
+ page_sizes = get_file_pages(b, num_pages)
144
+
145
+ pages = []
146
+ b.seek(8 + (num_pages * 4))
147
+ for ps in page_sizes:
148
+ # Grab individual pages and each page will contain >= one cookie
149
+ pages.append(b.read(ps))
150
+
151
+ for page in pages:
152
+ cookies = _deserialize_page(BytesIO(page))
153
+ all_cookies.extend(cookies)
154
+
155
+ return all_cookies
@@ -0,0 +1,142 @@
1
+ from datetime import datetime, timezone
2
+ from io import BufferedWriter, BytesIO
3
+ from struct import pack
4
+ from typing import BinaryIO, Dict, List, Tuple, Union
5
+
6
+ from binarycookies._deserialize import FLAGS
7
+ from binarycookies.models import BcField, Cookie, CookieFields, FileFields, Format
8
+
9
+ CookiesCollection = Union[List[Dict], List[Cookie], Tuple[Dict], Tuple[Cookie], Cookie, Dict[str, str]]
10
+
11
+
12
+ def date_to_mac_epoch(date: datetime) -> int:
13
+ """Converts a datetime object to mac epoch time."""
14
+ mac_epoch_start = datetime(2001, 1, 1, tzinfo=timezone.utc)
15
+ return int((date - mac_epoch_start).total_seconds())
16
+
17
+
18
+ def write_string(data: BytesIO, value: str):
19
+ """Writes a string to binary file."""
20
+ data.write(value.encode() + b"\x00")
21
+
22
+
23
+ def write_field(data: BytesIO, field: BcField, value: Union[str, int]):
24
+ """Writes a field to binary data."""
25
+ data.seek(field.offset)
26
+ if field.format == Format.string:
27
+ write_string(data, value)
28
+ else:
29
+ data.write(pack(field.format, value))
30
+
31
+
32
+ def serialize_cookie(cookie: Cookie) -> bytes:
33
+ """Serializes a cookie object to binary format."""
34
+ cookie_data = BytesIO()
35
+ cookie_fields = CookieFields()
36
+ # Write flag
37
+ write_field(cookie_data, cookie_fields.flag, list(FLAGS.keys())[list(FLAGS.values()).index(cookie.flag)])
38
+
39
+ # Calculate offsets
40
+ url_offset = 56 # The actual cookies content always starts at byte 56
41
+ name_offset = 1 + url_offset + len(cookie.url.encode("utf-8"))
42
+ path_offset = 1 + name_offset + len(cookie.name.encode("utf-8"))
43
+ value_offset = 1 + path_offset + len(cookie.path.encode("utf-8"))
44
+
45
+ write_field(cookie_data, cookie_fields.url_offset, url_offset)
46
+ write_field(cookie_data, cookie_fields.name_offset, name_offset)
47
+ write_field(cookie_data, cookie_fields.path_offset, path_offset)
48
+ write_field(cookie_data, cookie_fields.value_offset, value_offset)
49
+
50
+ write_field(cookie_data, cookie_fields.expiry_date, date_to_mac_epoch(cookie.expiry_datetime))
51
+ write_field(cookie_data, cookie_fields.create_date, date_to_mac_epoch(cookie.create_datetime))
52
+
53
+ # Write cookie data
54
+ write_string(cookie_data, cookie.url)
55
+ write_string(cookie_data, cookie.name)
56
+ write_string(cookie_data, cookie.path)
57
+ write_string(cookie_data, cookie.value)
58
+
59
+ # Write size at the beginning
60
+ size = len(cookie_data.getvalue())
61
+ cookie_data.seek(0)
62
+ cookie_data.write(pack(Format.integer, size))
63
+ return cookie_data.getvalue()
64
+
65
+
66
+ def dump(cookies: CookiesCollection, f: Union[BufferedWriter, BytesIO, BinaryIO]):
67
+ """Dumps a Binary Cookies object to create a binary cookies file.k
68
+
69
+ Args:
70
+ cookies: A Binary Cookies object to be serialized.
71
+ f: The file-like object to write the binary cookies data to.
72
+ """
73
+ binary = dumps(cookies)
74
+ f.write(binary)
75
+
76
+
77
+ def dumps(cookies: CookiesCollection) -> bytes:
78
+ """Dumps a Binary Cookies object to a byte string.
79
+ Args:
80
+ cookies: A Binary Cookies object to be serialized.
81
+ Returns:
82
+ bytes: The serialized binary cookies data.
83
+ """
84
+ if isinstance(cookies, dict):
85
+ cookies = [Cookie.model_validate(cookies)]
86
+ elif isinstance(cookies, (list, tuple)):
87
+ cookies = [Cookie.model_validate(cookie) for cookie in cookies]
88
+ elif isinstance(cookies, Cookie):
89
+ cookies = [cookies]
90
+ else:
91
+ raise TypeError("Invalid type for cookies. Expected dict, list, tuple, or Cookie.")
92
+
93
+ file_fields = FileFields()
94
+
95
+ data = BytesIO()
96
+
97
+ # Write file header
98
+ write_field(data, file_fields.header, "cook")
99
+
100
+ # Number of pages (1 for simplicity)
101
+ write_field(data, file_fields.num_pages, 1)
102
+
103
+ # Write number of cookies
104
+ data.write(pack(Format.integer, len(cookies)))
105
+
106
+ # Placeholder for page size
107
+ page_size_offset = data.tell()
108
+ data.write(b"\x00\x00\x00\x00")
109
+
110
+ # Write number of cookies
111
+ data.write(pack(Format.integer, len(cookies)))
112
+ cookie_data_list = []
113
+ # Write cookies
114
+ for cookie in cookies:
115
+ cookie_data_list.append(serialize_cookie(cookie))
116
+
117
+ initial_cookie_offset = data.tell() + (len(cookies) * 4)
118
+ initial_cookie = True
119
+ previous_sizes = 0
120
+ for cookie_data in cookie_data_list:
121
+ if initial_cookie:
122
+ data.write(pack(Format.integer, initial_cookie_offset))
123
+ initial_cookie = False
124
+ else:
125
+ data.write(pack(Format.integer, previous_sizes + initial_cookie_offset))
126
+
127
+ previous_sizes += len(cookie_data)
128
+
129
+ # Unknown data
130
+ data.write(b"\x00\x00\x00\x00")
131
+ data.write(b"\x00\x00\x00\x00")
132
+ data.write(b"\x00\x00\x00\x00")
133
+
134
+ for cookie_data in cookie_data_list:
135
+ data.write(cookie_data)
136
+
137
+ # Update page size
138
+ page_size = data.tell()
139
+ data.seek(page_size_offset)
140
+ data.write(pack(Format.integer, page_size))
141
+
142
+ return data.getvalue()
@@ -0,0 +1,57 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class BinaryCookiesDecodeError(Exception):
8
+ """Custom exception for binary cookies decoding errors."""
9
+
10
+ def __init__(self, message: str):
11
+ super().__init__(message)
12
+ self.message = message
13
+
14
+
15
+ class Flag(str, Enum):
16
+ SECURE = "Secure"
17
+ HTTPONLY = "HttpOnly"
18
+ UNKNOWN = "Unknown"
19
+ SECURE_HTTPONLY = "Secure; HttpOnly"
20
+
21
+
22
+ class Cookie(BaseModel):
23
+ name: str
24
+ value: str
25
+ url: str
26
+ path: str
27
+ create_datetime: datetime
28
+ expiry_datetime: datetime
29
+ flag: Flag
30
+
31
+
32
+ class Format(str, Enum):
33
+ integer = "<i" # Integer format is a 4 byte integer
34
+ integer_be = ">i" # Integer format is a 4 byte integer big endian
35
+ string = "<b" # String format is a byte
36
+ date = "<d" # Date format is a double (epoch mac)
37
+
38
+
39
+ class BcField(BaseModel):
40
+ offset: int
41
+ size: int
42
+ format: Format
43
+
44
+
45
+ class CookieFields(BaseModel):
46
+ flag: BcField = BcField(offset=8, size=4, format=Format.integer)
47
+ url_offset: BcField = BcField(offset=16, size=4, format=Format.integer)
48
+ name_offset: BcField = BcField(offset=20, size=4, format=Format.integer)
49
+ path_offset: BcField = BcField(offset=24, size=4, format=Format.integer)
50
+ value_offset: BcField = BcField(offset=28, size=4, format=Format.integer)
51
+ expiry_date: BcField = BcField(offset=40, size=8, format=Format.date)
52
+ create_date: BcField = BcField(offset=48, size=8, format=Format.date)
53
+
54
+
55
+ class FileFields(BaseModel):
56
+ header: BcField = BcField(offset=0, size=4, format=Format.string)
57
+ num_pages: BcField = BcField(offset=4, size=4, format=Format.integer_be)
@@ -0,0 +1,12 @@
1
+ from typing import List
2
+
3
+ from typing_extensions import deprecated
4
+
5
+ from binarycookies._deserialize import load
6
+ from binarycookies.models import Cookie
7
+
8
+
9
+ @deprecated("read_binary_cookies_file is deprecated, use binary_cookies_parser.load instead.")
10
+ def read_binary_cookies_file(file_path: str) -> List[Cookie]:
11
+ with open(file_path, "rb") as f:
12
+ return load(f)
File without changes