browserget 1.0.1__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.
Files changed (75) hide show
  1. browserget-1.0.1/.gitignore +31 -0
  2. browserget-1.0.1/CHANGELOG.md +54 -0
  3. browserget-1.0.1/LICENSE +21 -0
  4. browserget-1.0.1/PKG-INFO +165 -0
  5. browserget-1.0.1/README.md +126 -0
  6. browserget-1.0.1/browserget/__init__.py +46 -0
  7. browserget-1.0.1/browserget/__main__.py +6 -0
  8. browserget-1.0.1/browserget/archive.py +114 -0
  9. browserget-1.0.1/browserget/cache.py +187 -0
  10. browserget-1.0.1/browserget/checksum.py +76 -0
  11. browserget-1.0.1/browserget/cli.py +799 -0
  12. browserget-1.0.1/browserget/config.py +101 -0
  13. browserget-1.0.1/browserget/exceptions.py +110 -0
  14. browserget-1.0.1/browserget/http.py +272 -0
  15. browserget-1.0.1/browserget/installers/__init__.py +19 -0
  16. browserget-1.0.1/browserget/installers/base.py +129 -0
  17. browserget-1.0.1/browserget/installers/chrome.py +187 -0
  18. browserget-1.0.1/browserget/installers/chromedriver.py +214 -0
  19. browserget-1.0.1/browserget/installers/edge.py +507 -0
  20. browserget-1.0.1/browserget/installers/firefox.py +336 -0
  21. browserget-1.0.1/browserget/installers/geckodriver.py +200 -0
  22. browserget-1.0.1/browserget/logging.py +51 -0
  23. browserget-1.0.1/browserget/models.py +111 -0
  24. browserget-1.0.1/browserget/parsers/__init__.py +23 -0
  25. browserget-1.0.1/browserget/parsers/cft.py +119 -0
  26. browserget-1.0.1/browserget/parsers/edge.py +203 -0
  27. browserget-1.0.1/browserget/parsers/firefox.py +114 -0
  28. browserget-1.0.1/browserget/parsers/geckodriver.py +109 -0
  29. browserget-1.0.1/browserget/platform.py +142 -0
  30. browserget-1.0.1/browserget/registry.py +186 -0
  31. browserget-1.0.1/browserget/system.py +278 -0
  32. browserget-1.0.1/docs/architecture/overview.md +121 -0
  33. browserget-1.0.1/docs/architecture/parsers.md +113 -0
  34. browserget-1.0.1/docs/cli-reference/commands.md +82 -0
  35. browserget-1.0.1/docs/index.md +52 -0
  36. browserget-1.0.1/docs/usage/cache.md +147 -0
  37. browserget-1.0.1/docs/usage/ensure.md +116 -0
  38. browserget-1.0.1/docs/usage/install.md +122 -0
  39. browserget-1.0.1/mkdocs.yml +51 -0
  40. browserget-1.0.1/pyproject.toml +92 -0
  41. browserget-1.0.1/tests/__init__.py +0 -0
  42. browserget-1.0.1/tests/cli/__init__.py +0 -0
  43. browserget-1.0.1/tests/cli/conftest.py +110 -0
  44. browserget-1.0.1/tests/cli/test_doctor.py +211 -0
  45. browserget-1.0.1/tests/cli/test_ensure.py +157 -0
  46. browserget-1.0.1/tests/cli/test_install.py +205 -0
  47. browserget-1.0.1/tests/cli/test_list.py +105 -0
  48. browserget-1.0.1/tests/cli/test_path.py +80 -0
  49. browserget-1.0.1/tests/cli/test_remove.py +122 -0
  50. browserget-1.0.1/tests/cli/test_versions.py +85 -0
  51. browserget-1.0.1/tests/conftest.py +58 -0
  52. browserget-1.0.1/tests/fixtures/cft_versions.json +202 -0
  53. browserget-1.0.1/tests/fixtures/edge_api.json +152 -0
  54. browserget-1.0.1/tests/fixtures/firefox_releases.html +18 -0
  55. browserget-1.0.1/tests/fixtures/geckodriver_releases.json +76 -0
  56. browserget-1.0.1/tests/integration/__init__.py +0 -0
  57. browserget-1.0.1/tests/integration/conftest.py +64 -0
  58. browserget-1.0.1/tests/integration/test_real_install.py +459 -0
  59. browserget-1.0.1/tests/unit/__init__.py +0 -0
  60. browserget-1.0.1/tests/unit/test_archive.py +127 -0
  61. browserget-1.0.1/tests/unit/test_checksum.py +94 -0
  62. browserget-1.0.1/tests/unit/test_exceptions.py +272 -0
  63. browserget-1.0.1/tests/unit/test_http.py +239 -0
  64. browserget-1.0.1/tests/unit/test_models.py +200 -0
  65. browserget-1.0.1/tests/unit/test_parsers/__init__.py +0 -0
  66. browserget-1.0.1/tests/unit/test_parsers/test_cft.py +142 -0
  67. browserget-1.0.1/tests/unit/test_parsers/test_edge.py +175 -0
  68. browserget-1.0.1/tests/unit/test_parsers/test_firefox.py +154 -0
  69. browserget-1.0.1/tests/unit/test_parsers/test_geckodriver.py +175 -0
  70. browserget-1.0.1/tests/unit/test_platform.py +129 -0
  71. browserget-1.0.1/tests/unit/test_registry.py +277 -0
  72. browserget-1.0.1/tests/unit/test_regression_phase2.py +307 -0
  73. browserget-1.0.1/tests/unit/test_regression_phase3.py +1593 -0
  74. browserget-1.0.1/tests/unit/test_regression_phase4.py +1783 -0
  75. browserget-1.0.1/tests/unit/test_system.py +227 -0
@@ -0,0 +1,31 @@
1
+ ref/
2
+ __pycache__/
3
+ *.pyc
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.egg
9
+ .tox/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ .pytest_cache/
13
+ htmlcov/
14
+ .coverage
15
+ .coverage.*
16
+ venv/
17
+ .venv/
18
+ .env
19
+ .venv-dev/
20
+
21
+ # Additional ignores
22
+ coverage.xml
23
+ *.so
24
+ .idea/
25
+ .vscode/
26
+ *.swp
27
+ *.swo
28
+ site/
29
+ spike.py
30
+ errors.txt
31
+ out.yml
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.1] - 2026-08-17
11
+
12
+ ### Fixed
13
+
14
+ - Corrected PyPI publish action reference in release workflow
15
+
16
+ ## [1.0.0] - 2026-08-17
17
+
18
+ ### Added
19
+
20
+ - Project scaffolding: directory structure, community files, CI workflows
21
+ - `pyproject.toml` with `dev` and `docs` optional dependencies
22
+ - GitHub Actions: CI (lint + matrix test), coverage, docs deploy
23
+ - Issue templates (bug report, feature request) and PR template
24
+ - Dependabot configuration for pip and github-actions
25
+ - Pre-commit hooks (pre-commit-hooks v4.6.0, ruff-pre-commit v0.8.0)
26
+ - EditorConfig, Makefile, AGENTS.md
27
+ - Public API exports in `browserget.__init__` and `browserget.installers`
28
+
29
+ ### Fixed
30
+
31
+ - Platform mapping for edge/edgedriver now returns lowercase strings matching
32
+ the Edge API (`win64`, `mac-arm64`, `linux64`)
33
+ - Firefox parser `_PLATFORM_PATHS` now includes `"os"` mapping for macOS
34
+ - GeckoDriver parser `_ASSET_PLATFORM_MAP` now includes `"macos"` mapping
35
+ - `InstalledArtifact.from_dict` validates required fields and types
36
+ - `Registry.load` skips corrupted entries instead of crashing
37
+ - HTTP requests now include `User-Agent` header for API compatibility
38
+ - `get_available_disk_mb` consistently uses `get_cache_dir()` for path
39
+ - `UnknownTargetError` message now includes `edgedriver` in supported list
40
+ - `zip` calls in CLI use `strict=True` for safety
41
+ - Bare `Exception` catches in `doctor` command replaced with specific types
42
+ - `subprocess.run` in Edge installer uses `text=True` for cleaner output
43
+
44
+ ### Changed
45
+
46
+ - Removed duplicate `__init__` methods from all installer subclasses (DRY)
47
+ - Extracted common HTTP retry logic into `_retry_with_backoff` method (DRY)
48
+ - `logging.py` now also configures stdlib `urllib` logger
49
+ - CI workflow `test.yml` coverage threshold adjusted from 80% to 50%
50
+ - Consolidated CI workflows: merged `test.yml` into `ci.yml`, added `ruff format --check`
51
+ - `__version__` now uses `importlib.metadata` instead of hardcoded string
52
+ - Development status updated to Production/Stable
53
+ - Package layout moved from `src/browserget` to `browserget/` repository root
54
+ - Author email standardized to `mathias.paulenko@outlook.com` across all files
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mathias Paulenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.5
2
+ Name: browserget
3
+ Version: 1.0.1
4
+ Summary: Standalone CLI to install browsers and drivers without any framework
5
+ Project-URL: Homepage, https://mathiaspaulenko.github.io/browserget/
6
+ Project-URL: Repository, https://github.com/MathiasPaulenko/browserget
7
+ Project-URL: Changelog, https://github.com/MathiasPaulenko/browserget/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/MathiasPaulenko/browserget/issues
9
+ Author-email: Mathias Paulenko <mathias.paulenko@outlook.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: automation,browser,chrome,chromedriver,edge,edgedriver,firefox,geckodriver,install,selenium,testing,webdriver
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Testing
23
+ Classifier: Topic :: Utilities
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: typer>=0.12.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: build>=1.2; extra == 'dev'
29
+ Requires-Dist: mypy>=1.10; extra == 'dev'
30
+ Requires-Dist: pre-commit>=3.7; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
32
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
33
+ Requires-Dist: pytest>=8.0; extra == 'dev'
34
+ Requires-Dist: ruff>=0.8; extra == 'dev'
35
+ Provides-Extra: docs
36
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
37
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # browserget
41
+
42
+ [![CI](https://github.com/MathiasPaulenko/browserget/actions/workflows/ci.yml/badge.svg)](https://github.com/MathiasPaulenko/browserget/actions/workflows/ci.yml)
43
+ [![Docs](https://github.com/MathiasPaulenko/browserget/actions/workflows/docs.yml/badge.svg)](https://mathiaspaulenko.github.io/browserget/)
44
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
45
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
46
+ [![PyPI](https://img.shields.io/pypi/v/browserget.svg)](https://pypi.org/project/browserget/)
47
+
48
+ Standalone CLI to install browsers and drivers without any framework.
49
+
50
+ Chrome for Testing, Firefox, Edge, ChromeDriver, GeckoDriver, EdgeDriver —
51
+ all from a single tool. Python 3.11+, zero heavy dependencies.
52
+
53
+ ## Why?
54
+
55
+ `playwright install` ties you to Playwright. `puppeteer install` ties you to
56
+ Puppeteer. `browserget` installs browsers for **any** use — automation,
57
+ scraping, testing, CDP, BiDi, whatever.
58
+
59
+ ## Features
60
+
61
+ - **6 targets** — Chrome, Firefox, Edge + ChromeDriver, GeckoDriver, EdgeDriver
62
+ - **Concurrent installs** — multiple targets install in parallel
63
+ - **Driver matching** — `--for chrome` auto-matches ChromeDriver to your Chrome
64
+ - **Checksum verification** — SHA-256/SHA-512 when upstream provides checksums
65
+ - **Registry tracking** — JSON registry tracks every installed artifact
66
+ - **JSON output** — every command supports `--json` for CI/CD integration
67
+ - **Retry with backoff** — HTTP client retries failed requests automatically
68
+ - **Disk space checks** — verifies sufficient space before downloading
69
+ - **Platform detection** — auto-detects OS and architecture
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ pip install browserget
75
+ ```
76
+
77
+ ## Quick start
78
+
79
+ ```bash
80
+ # Install latest stable Chrome
81
+ browserget install chrome
82
+
83
+ # Install matching ChromeDriver
84
+ browserget install chromedriver --for chrome
85
+
86
+ # Ensure browsers are installed (idempotent — skips if present)
87
+ browserget ensure chrome chromedriver
88
+
89
+ # Verify system health
90
+ browserget doctor
91
+ ```
92
+
93
+ ## Usage
94
+
95
+ ```bash
96
+ # Install specific version
97
+ browserget install chrome --version 131.0.6778.87
98
+
99
+ # Install by milestone (latest patch in 131)
100
+ browserget install chrome --version 131
101
+
102
+ # Install Firefox and Edge
103
+ browserget install firefox edge
104
+
105
+ # Install EdgeDriver
106
+ browserget install edgedriver --for edge
107
+
108
+ # List installed browsers
109
+ browserget list
110
+
111
+ # Get path to installed browser
112
+ browserget path chrome
113
+
114
+ # Remove a browser
115
+ browserget remove chrome
116
+
117
+ # Remove all versions of a target
118
+ browserget remove chrome --all
119
+
120
+ # List available versions for a target
121
+ browserget versions chrome
122
+
123
+ # Check system health
124
+ browserget doctor
125
+
126
+ # JSON output for CI/CD
127
+ browserget install chrome --json
128
+ ```
129
+
130
+ ## Supported targets
131
+
132
+ | Target | Type | Upstream source |
133
+ |--------|------|-----------------|
134
+ | `chrome` | Browser | Chrome for Testing API |
135
+ | `firefox` | Browser | Mozilla FTP |
136
+ | `edge` | Browser | Edge Updates API |
137
+ | `chromedriver` | Driver | Chrome for Testing API |
138
+ | `geckodriver` | Driver | GitHub Releases (mozilla/geckodriver) |
139
+ | `edgedriver` | Driver | Edge Updates API |
140
+
141
+ ## Requirements
142
+
143
+ - Python 3.11, 3.12, or 3.13
144
+ - `typer` (installed automatically)
145
+ - Internet access to download browser binaries
146
+
147
+ ## Documentation
148
+
149
+ Full documentation is available at
150
+ [https://mathiaspaulenko.github.io/browserget/](https://mathiaspaulenko.github.io/browserget/).
151
+
152
+ - [Install command](https://mathiaspaulenko.github.io/browserget/usage/install/) — detailed usage and examples
153
+ - [Ensure command](https://mathiaspaulenko.github.io/browserget/usage/ensure/) — idempotent installs for CI pipelines
154
+ - [Cache management](https://mathiaspaulenko.github.io/browserget/usage/cache/) — cache location, cleanup, and configuration
155
+ - [CLI reference](https://mathiaspaulenko.github.io/browserget/cli-reference/commands/) — complete command and flag documentation
156
+ - [Architecture](https://mathiaspaulenko.github.io/browserget/architecture/overview/) — module diagram and design decisions
157
+
158
+ ## Contributing
159
+
160
+ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for
161
+ development setup, testing, and pull request guidelines.
162
+
163
+ ## License
164
+
165
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,126 @@
1
+ # browserget
2
+
3
+ [![CI](https://github.com/MathiasPaulenko/browserget/actions/workflows/ci.yml/badge.svg)](https://github.com/MathiasPaulenko/browserget/actions/workflows/ci.yml)
4
+ [![Docs](https://github.com/MathiasPaulenko/browserget/actions/workflows/docs.yml/badge.svg)](https://mathiaspaulenko.github.io/browserget/)
5
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
+ [![PyPI](https://img.shields.io/pypi/v/browserget.svg)](https://pypi.org/project/browserget/)
8
+
9
+ Standalone CLI to install browsers and drivers without any framework.
10
+
11
+ Chrome for Testing, Firefox, Edge, ChromeDriver, GeckoDriver, EdgeDriver —
12
+ all from a single tool. Python 3.11+, zero heavy dependencies.
13
+
14
+ ## Why?
15
+
16
+ `playwright install` ties you to Playwright. `puppeteer install` ties you to
17
+ Puppeteer. `browserget` installs browsers for **any** use — automation,
18
+ scraping, testing, CDP, BiDi, whatever.
19
+
20
+ ## Features
21
+
22
+ - **6 targets** — Chrome, Firefox, Edge + ChromeDriver, GeckoDriver, EdgeDriver
23
+ - **Concurrent installs** — multiple targets install in parallel
24
+ - **Driver matching** — `--for chrome` auto-matches ChromeDriver to your Chrome
25
+ - **Checksum verification** — SHA-256/SHA-512 when upstream provides checksums
26
+ - **Registry tracking** — JSON registry tracks every installed artifact
27
+ - **JSON output** — every command supports `--json` for CI/CD integration
28
+ - **Retry with backoff** — HTTP client retries failed requests automatically
29
+ - **Disk space checks** — verifies sufficient space before downloading
30
+ - **Platform detection** — auto-detects OS and architecture
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install browserget
36
+ ```
37
+
38
+ ## Quick start
39
+
40
+ ```bash
41
+ # Install latest stable Chrome
42
+ browserget install chrome
43
+
44
+ # Install matching ChromeDriver
45
+ browserget install chromedriver --for chrome
46
+
47
+ # Ensure browsers are installed (idempotent — skips if present)
48
+ browserget ensure chrome chromedriver
49
+
50
+ # Verify system health
51
+ browserget doctor
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ ```bash
57
+ # Install specific version
58
+ browserget install chrome --version 131.0.6778.87
59
+
60
+ # Install by milestone (latest patch in 131)
61
+ browserget install chrome --version 131
62
+
63
+ # Install Firefox and Edge
64
+ browserget install firefox edge
65
+
66
+ # Install EdgeDriver
67
+ browserget install edgedriver --for edge
68
+
69
+ # List installed browsers
70
+ browserget list
71
+
72
+ # Get path to installed browser
73
+ browserget path chrome
74
+
75
+ # Remove a browser
76
+ browserget remove chrome
77
+
78
+ # Remove all versions of a target
79
+ browserget remove chrome --all
80
+
81
+ # List available versions for a target
82
+ browserget versions chrome
83
+
84
+ # Check system health
85
+ browserget doctor
86
+
87
+ # JSON output for CI/CD
88
+ browserget install chrome --json
89
+ ```
90
+
91
+ ## Supported targets
92
+
93
+ | Target | Type | Upstream source |
94
+ |--------|------|-----------------|
95
+ | `chrome` | Browser | Chrome for Testing API |
96
+ | `firefox` | Browser | Mozilla FTP |
97
+ | `edge` | Browser | Edge Updates API |
98
+ | `chromedriver` | Driver | Chrome for Testing API |
99
+ | `geckodriver` | Driver | GitHub Releases (mozilla/geckodriver) |
100
+ | `edgedriver` | Driver | Edge Updates API |
101
+
102
+ ## Requirements
103
+
104
+ - Python 3.11, 3.12, or 3.13
105
+ - `typer` (installed automatically)
106
+ - Internet access to download browser binaries
107
+
108
+ ## Documentation
109
+
110
+ Full documentation is available at
111
+ [https://mathiaspaulenko.github.io/browserget/](https://mathiaspaulenko.github.io/browserget/).
112
+
113
+ - [Install command](https://mathiaspaulenko.github.io/browserget/usage/install/) — detailed usage and examples
114
+ - [Ensure command](https://mathiaspaulenko.github.io/browserget/usage/ensure/) — idempotent installs for CI pipelines
115
+ - [Cache management](https://mathiaspaulenko.github.io/browserget/usage/cache/) — cache location, cleanup, and configuration
116
+ - [CLI reference](https://mathiaspaulenko.github.io/browserget/cli-reference/commands/) — complete command and flag documentation
117
+ - [Architecture](https://mathiaspaulenko.github.io/browserget/architecture/overview/) — module diagram and design decisions
118
+
119
+ ## Contributing
120
+
121
+ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for
122
+ development setup, testing, and pull request guidelines.
123
+
124
+ ## License
125
+
126
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,46 @@
1
+ """browserget — Standalone CLI to install browsers and drivers without any framework."""
2
+
3
+ from importlib.metadata import PackageNotFoundError
4
+ from importlib.metadata import version as _pkg_version
5
+
6
+ from browserget.config import Config, load_config
7
+ from browserget.exceptions import (
8
+ AlreadyInstalledError,
9
+ BrowsergetError,
10
+ ChecksumMismatchError,
11
+ DriverMatchError,
12
+ InsufficientDiskSpaceError,
13
+ NetworkError,
14
+ UnknownTargetError,
15
+ UnsupportedPlatformError,
16
+ VersionNotFoundError,
17
+ )
18
+ from browserget.models import InstalledArtifact, ResolvedVersion, SystemBrowser
19
+ from browserget.platform import OS, Arch, Platform, detect_platform
20
+
21
+ try:
22
+ __version__ = _pkg_version("browserget")
23
+ except PackageNotFoundError: # pragma: no cover
24
+ __version__ = "0.0.0"
25
+
26
+ __all__ = [
27
+ "AlreadyInstalledError",
28
+ "Arch",
29
+ "BrowsergetError",
30
+ "ChecksumMismatchError",
31
+ "Config",
32
+ "DriverMatchError",
33
+ "InsufficientDiskSpaceError",
34
+ "InstalledArtifact",
35
+ "NetworkError",
36
+ "OS",
37
+ "Platform",
38
+ "ResolvedVersion",
39
+ "SystemBrowser",
40
+ "UnknownTargetError",
41
+ "UnsupportedPlatformError",
42
+ "VersionNotFoundError",
43
+ "__version__",
44
+ "detect_platform",
45
+ "load_config",
46
+ ]
@@ -0,0 +1,6 @@
1
+ """Entry point for running browserget as a module."""
2
+
3
+ from browserget.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,114 @@
1
+ """Safe archive extraction utilities with path-traversal protection.
2
+
3
+ All extraction functions validate that every entry in the archive resolves
4
+ to a path inside the destination directory, preventing Zip Slip and similar
5
+ path-traversal attacks.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import stat
12
+ import sys
13
+ import tarfile
14
+ import zipfile
15
+ from pathlib import Path
16
+
17
+
18
+ def _safe_extract_path(dest: Path, member_name: str) -> Path:
19
+ """Resolve *member_name* relative to *dest* and verify it stays inside.
20
+
21
+ Args:
22
+ dest: The destination directory.
23
+ member_name: The archive member's relative path.
24
+
25
+ Returns:
26
+ The resolved, validated path.
27
+
28
+ Raises:
29
+ ValueError: If the resolved path escapes *dest*.
30
+ """
31
+ dest_resolved = dest.resolve()
32
+ target = (dest / member_name).resolve()
33
+ try:
34
+ target.relative_to(dest_resolved)
35
+ except ValueError as exc:
36
+ raise ValueError(f"Archive entry '{member_name}' escapes destination directory") from exc
37
+ return target
38
+
39
+
40
+ def extract_zip(archive_path: Path, dest: Path) -> None:
41
+ """Extract a zip archive safely, preventing path traversal.
42
+
43
+ Validates both member paths and symlink targets to prevent
44
+ path-traversal attacks via malicious symlinks.
45
+
46
+ Args:
47
+ archive_path: Path to the zip file.
48
+ dest: Destination directory (must exist).
49
+
50
+ Raises:
51
+ ValueError: If any entry escapes the destination directory.
52
+ zipfile.BadZipFile: If the archive is corrupt or not a zip file.
53
+ """
54
+ dest.mkdir(parents=True, exist_ok=True)
55
+ with zipfile.ZipFile(archive_path, "r") as zf:
56
+ for member in zf.infolist():
57
+ _safe_extract_path(dest, member.filename)
58
+ unix_mode = member.external_attr >> 16
59
+ if stat.S_ISLNK(unix_mode):
60
+ link_target = zf.read(member).decode("utf-8", errors="replace")
61
+ _safe_extract_path(dest, link_target)
62
+ zf.extractall(dest)
63
+
64
+
65
+ def extract_tar(archive_path: Path, dest: Path) -> None:
66
+ """Extract a tar archive safely, preventing path traversal.
67
+
68
+ Automatically detects compression (gzip, bzip2, xz, or uncompressed).
69
+ Validates both member paths and symlink/hardlink targets to prevent
70
+ path-traversal attacks via malicious symlinks.
71
+
72
+ Args:
73
+ archive_path: Path to the tar file.
74
+ dest: Destination directory (must exist).
75
+
76
+ Raises:
77
+ ValueError: If any entry escapes the destination directory.
78
+ tarfile.TarError: If the archive is corrupt.
79
+ """
80
+ dest.mkdir(parents=True, exist_ok=True)
81
+ with tarfile.open(str(archive_path), "r:*") as tf:
82
+ for member in tf.getmembers():
83
+ _safe_extract_path(dest, member.name)
84
+ if member.issym() or member.islnk():
85
+ _safe_extract_path(dest, member.linkname)
86
+ if sys.version_info >= (3, 12):
87
+ tf.extractall(dest, filter="data")
88
+ else:
89
+ for member in tf.getmembers():
90
+ if not (member.isfile() or member.isdir() or member.issym() or member.islnk()):
91
+ raise ValueError(f"Refusing to extract special file: {member.name!r}")
92
+ tf.extractall(dest)
93
+
94
+
95
+ def find_file_by_name(root: Path, filename: str) -> Path | None:
96
+ """Find a file by name in a directory tree without following symlinks.
97
+
98
+ Uses ``os.walk(followlinks=False)`` to avoid infinite recursion from
99
+ circular symlinks that may exist in extracted archives.
100
+
101
+ Args:
102
+ root: The root directory to search.
103
+ filename: The filename to match (basename only).
104
+
105
+ Returns:
106
+ The first matching file path, or ``None`` if not found.
107
+ """
108
+ for dirpath, _dirnames, filenames in os.walk(root, followlinks=False):
109
+ for fname in filenames:
110
+ if fname == filename:
111
+ candidate = Path(dirpath) / fname
112
+ if not candidate.is_symlink() and candidate.is_file():
113
+ return candidate
114
+ return None