mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
feat: Windows .zip download support, binary v145.0.7632.109.2
- Add get_archive_ext() / get_archive_name() for platform-aware archive format (.zip on Windows, .tar.gz elsewhere) - Add _extract_zip() / extractZip() with path traversal protection - Python: zipfile module extraction - JS: PowerShell Expand-Archive on Windows, system unzip on others - Bump all platform versions to 145.0.7632.109.2 (4 platforms: linux-x64, darwin-arm64, darwin-x64, windows-x64) - Update checksum lookup, temp file naming, and auto-update asset matching to use archive helpers
This commit is contained in:
+18
-9
@@ -15,13 +15,13 @@ from ._version import __version__
|
|||||||
# CHROMIUM_VERSION is the latest across all platforms (for display/reference).
|
# CHROMIUM_VERSION is the latest across all platforms (for display/reference).
|
||||||
# Use get_chromium_version() for the current platform's actual version.
|
# Use get_chromium_version() for the current platform's actual version.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
CHROMIUM_VERSION = "145.0.7632.109"
|
CHROMIUM_VERSION = "145.0.7632.109.2"
|
||||||
|
|
||||||
PLATFORM_CHROMIUM_VERSIONS: dict[str, str] = {
|
PLATFORM_CHROMIUM_VERSIONS: dict[str, str] = {
|
||||||
"linux-x64": "145.0.7632.109",
|
"linux-x64": "145.0.7632.109.2",
|
||||||
"darwin-arm64": "145.0.7632.109",
|
"darwin-arm64": "145.0.7632.109.2",
|
||||||
"darwin-x64": "145.0.7632.109",
|
"darwin-x64": "145.0.7632.109.2",
|
||||||
"windows-x64": "145.0.7632.109",
|
"windows-x64": "145.0.7632.109.2",
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -204,18 +204,27 @@ GITHUB_DOWNLOAD_BASE_URL = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_archive_ext() -> str:
|
||||||
|
"""Return the archive extension for the current platform (.zip for Windows, .tar.gz otherwise)."""
|
||||||
|
return ".zip" if platform.system() == "Windows" else ".tar.gz"
|
||||||
|
|
||||||
|
|
||||||
|
def get_archive_name(tag: str | None = None) -> str:
|
||||||
|
"""Return the archive filename for a platform tag (e.g. 'cloakbrowser-linux-x64.tar.gz')."""
|
||||||
|
t = tag or get_platform_tag()
|
||||||
|
return f"cloakbrowser-{t}{get_archive_ext()}"
|
||||||
|
|
||||||
|
|
||||||
def get_download_url(version: str | None = None) -> str:
|
def get_download_url(version: str | None = None) -> str:
|
||||||
"""Return the full download URL for the current platform's binary archive."""
|
"""Return the full download URL for the current platform's binary archive."""
|
||||||
v = version or get_chromium_version()
|
v = version or get_chromium_version()
|
||||||
tag = get_platform_tag()
|
return f"{DOWNLOAD_BASE_URL}/chromium-v{v}/{get_archive_name()}"
|
||||||
return f"{DOWNLOAD_BASE_URL}/chromium-v{v}/cloakbrowser-{tag}.tar.gz"
|
|
||||||
|
|
||||||
|
|
||||||
def get_fallback_download_url(version: str | None = None) -> str:
|
def get_fallback_download_url(version: str | None = None) -> str:
|
||||||
"""Return the GitHub Releases fallback URL for the binary archive."""
|
"""Return the GitHub Releases fallback URL for the binary archive."""
|
||||||
v = version or get_chromium_version()
|
v = version or get_chromium_version()
|
||||||
tag = get_platform_tag()
|
return f"{GITHUB_DOWNLOAD_BASE_URL}/chromium-v{v}/{get_archive_name()}"
|
||||||
return f"{GITHUB_DOWNLOAD_BASE_URL}/chromium-v{v}/cloakbrowser-{tag}.tar.gz"
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
+43
-23
@@ -28,6 +28,8 @@ from .config import (
|
|||||||
GITHUB_DOWNLOAD_BASE_URL,
|
GITHUB_DOWNLOAD_BASE_URL,
|
||||||
_version_newer,
|
_version_newer,
|
||||||
check_platform_available,
|
check_platform_available,
|
||||||
|
get_archive_ext,
|
||||||
|
get_archive_name,
|
||||||
get_binary_dir,
|
get_binary_dir,
|
||||||
get_binary_path,
|
get_binary_path,
|
||||||
get_cache_dir,
|
get_cache_dir,
|
||||||
@@ -123,7 +125,7 @@ def _download_and_extract(version: str | None = None) -> None:
|
|||||||
binary_dir.parent.mkdir(parents=True, exist_ok=True)
|
binary_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Download to temp file first (atomic — no partial downloads in cache)
|
# Download to temp file first (atomic — no partial downloads in cache)
|
||||||
with tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False) as tmp:
|
with tempfile.NamedTemporaryFile(suffix=get_archive_ext(), delete=False) as tmp:
|
||||||
tmp_path = Path(tmp.name)
|
tmp_path = Path(tmp.name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -155,7 +157,7 @@ def _download_and_extract(version: str | None = None) -> None:
|
|||||||
def _verify_download_checksum(file_path: Path, version: str | None = None) -> None:
|
def _verify_download_checksum(file_path: Path, version: str | None = None) -> None:
|
||||||
"""Fetch SHA256SUMS and verify the downloaded file. Warn if unavailable, fail on mismatch."""
|
"""Fetch SHA256SUMS and verify the downloaded file. Warn if unavailable, fail on mismatch."""
|
||||||
checksums = _fetch_checksums(version)
|
checksums = _fetch_checksums(version)
|
||||||
tarball_name = f"cloakbrowser-{get_platform_tag()}.tar.gz"
|
tarball_name = get_archive_name()
|
||||||
|
|
||||||
if checksums is None:
|
if checksums is None:
|
||||||
logger.warning("SHA256SUMS not available for this release — skipping checksum verification")
|
logger.warning("SHA256SUMS not available for this release — skipping checksum verification")
|
||||||
@@ -256,7 +258,7 @@ def _download_file(url: str, dest: Path) -> None:
|
|||||||
def _extract_archive(
|
def _extract_archive(
|
||||||
archive_path: Path, dest_dir: Path, binary_path: Path | None = None
|
archive_path: Path, dest_dir: Path, binary_path: Path | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Extract tar.gz archive to destination directory."""
|
"""Extract tar.gz or zip archive to destination directory."""
|
||||||
logger.info("Extracting to %s", dest_dir)
|
logger.info("Extracting to %s", dest_dir)
|
||||||
|
|
||||||
# Clean existing dir if partial download existed
|
# Clean existing dir if partial download existed
|
||||||
@@ -266,26 +268,12 @@ def _extract_archive(
|
|||||||
|
|
||||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
with tarfile.open(archive_path, "r:gz") as tar:
|
if str(archive_path).endswith(".zip"):
|
||||||
# Security: prevent path traversal
|
_extract_zip(archive_path, dest_dir)
|
||||||
safe_members = []
|
else:
|
||||||
for member in tar.getmembers():
|
_extract_tar(archive_path, dest_dir)
|
||||||
# Allow symlinks — macOS .app bundles require them (Framework layout)
|
|
||||||
if member.issym() or member.islnk():
|
|
||||||
link_target = member.linkname
|
|
||||||
# Reject symlinks that escape the dest dir
|
|
||||||
if os.path.isabs(link_target) or ".." in link_target.split("/"):
|
|
||||||
logger.warning("Skipping suspicious symlink: %s -> %s", member.name, link_target)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
member_path = (dest_dir / member.name).resolve()
|
|
||||||
if not str(member_path).startswith(str(dest_dir.resolve())):
|
|
||||||
raise RuntimeError(f"Archive contains path traversal: {member.name}")
|
|
||||||
safe_members.append(member)
|
|
||||||
|
|
||||||
tar.extractall(dest_dir, members=safe_members)
|
# If extracted into a single subdirectory, flatten it
|
||||||
|
|
||||||
# If tar extracted into a single subdirectory, flatten it
|
|
||||||
# (e.g. fingerprint-chromium-142-custom-v2/chrome → chrome)
|
# (e.g. fingerprint-chromium-142-custom-v2/chrome → chrome)
|
||||||
# But never flatten .app bundles — macOS needs the bundle structure intact
|
# But never flatten .app bundles — macOS needs the bundle structure intact
|
||||||
_flatten_single_subdir(dest_dir)
|
_flatten_single_subdir(dest_dir)
|
||||||
@@ -303,6 +291,38 @@ def _extract_archive(
|
|||||||
logger.info("Binary ready: %s", bp)
|
logger.info("Binary ready: %s", bp)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_tar(archive_path: Path, dest_dir: Path) -> None:
|
||||||
|
"""Extract tar.gz archive with path traversal protection."""
|
||||||
|
with tarfile.open(archive_path, "r:gz") as tar:
|
||||||
|
safe_members = []
|
||||||
|
for member in tar.getmembers():
|
||||||
|
# Allow symlinks — macOS .app bundles require them (Framework layout)
|
||||||
|
if member.issym() or member.islnk():
|
||||||
|
link_target = member.linkname
|
||||||
|
if os.path.isabs(link_target) or ".." in link_target.split("/"):
|
||||||
|
logger.warning("Skipping suspicious symlink: %s -> %s", member.name, link_target)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
member_path = (dest_dir / member.name).resolve()
|
||||||
|
if not str(member_path).startswith(str(dest_dir.resolve())):
|
||||||
|
raise RuntimeError(f"Archive contains path traversal: {member.name}")
|
||||||
|
safe_members.append(member)
|
||||||
|
|
||||||
|
tar.extractall(dest_dir, members=safe_members)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_zip(archive_path: Path, dest_dir: Path) -> None:
|
||||||
|
"""Extract zip archive with path traversal protection."""
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
with zipfile.ZipFile(archive_path, "r") as zf:
|
||||||
|
for info in zf.infolist():
|
||||||
|
member_path = (dest_dir / info.filename).resolve()
|
||||||
|
if not str(member_path).startswith(str(dest_dir.resolve())):
|
||||||
|
raise RuntimeError(f"Archive contains path traversal: {info.filename}")
|
||||||
|
zf.extractall(dest_dir)
|
||||||
|
|
||||||
|
|
||||||
def _flatten_single_subdir(dest_dir: Path) -> None:
|
def _flatten_single_subdir(dest_dir: Path) -> None:
|
||||||
"""If extraction created a single subdirectory, move its contents up.
|
"""If extraction created a single subdirectory, move its contents up.
|
||||||
|
|
||||||
@@ -435,7 +455,7 @@ def _get_latest_chromium_version() -> str | None:
|
|||||||
GITHUB_API_URL, params={"per_page": 10}, timeout=10.0
|
GITHUB_API_URL, params={"per_page": 10}, timeout=10.0
|
||||||
)
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
platform_tarball = f"cloakbrowser-{get_platform_tag()}.tar.gz"
|
platform_tarball = get_archive_name()
|
||||||
for release in resp.json():
|
for release in resp.json():
|
||||||
tag = release.get("tag_name", "")
|
tag = release.get("tag_name", "")
|
||||||
if tag.startswith("chromium-v") and not release.get("draft"):
|
if tag.startswith("chromium-v") and not release.get("draft"):
|
||||||
|
|||||||
+15
-9
@@ -27,13 +27,13 @@ export { WRAPPER_VERSION };
|
|||||||
// CHROMIUM_VERSION is the latest across all platforms (for display/reference).
|
// CHROMIUM_VERSION is the latest across all platforms (for display/reference).
|
||||||
// Use getChromiumVersion() for the current platform's actual version.
|
// Use getChromiumVersion() for the current platform's actual version.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
export const CHROMIUM_VERSION = "145.0.7632.109";
|
export const CHROMIUM_VERSION = "145.0.7632.109.2";
|
||||||
|
|
||||||
export const PLATFORM_CHROMIUM_VERSIONS: Record<string, string> = {
|
export const PLATFORM_CHROMIUM_VERSIONS: Record<string, string> = {
|
||||||
"linux-x64": "145.0.7632.109",
|
"linux-x64": "145.0.7632.109.2",
|
||||||
"darwin-arm64": "145.0.7632.109",
|
"darwin-arm64": "145.0.7632.109.2",
|
||||||
"darwin-x64": "145.0.7632.109",
|
"darwin-x64": "145.0.7632.109.2",
|
||||||
"windows-x64": "145.0.7632.109",
|
"windows-x64": "145.0.7632.109.2",
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -126,16 +126,22 @@ export const GITHUB_API_URL =
|
|||||||
export const GITHUB_DOWNLOAD_BASE_URL =
|
export const GITHUB_DOWNLOAD_BASE_URL =
|
||||||
"https://github.com/CloakHQ/cloakbrowser/releases/download";
|
"https://github.com/CloakHQ/cloakbrowser/releases/download";
|
||||||
|
|
||||||
|
export function getArchiveExt(): string {
|
||||||
|
return process.platform === "win32" ? ".zip" : ".tar.gz";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getArchiveName(tag?: string): string {
|
||||||
|
return `cloakbrowser-${tag || getPlatformTag()}${getArchiveExt()}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function getDownloadUrl(version?: string): string {
|
export function getDownloadUrl(version?: string): string {
|
||||||
const v = version || getChromiumVersion();
|
const v = version || getChromiumVersion();
|
||||||
const tag = getPlatformTag();
|
return `${DOWNLOAD_BASE_URL}/chromium-v${v}/${getArchiveName()}`;
|
||||||
return `${DOWNLOAD_BASE_URL}/chromium-v${v}/cloakbrowser-${tag}.tar.gz`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFallbackDownloadUrl(version?: string): string {
|
export function getFallbackDownloadUrl(version?: string): string {
|
||||||
const v = version || getChromiumVersion();
|
const v = version || getChromiumVersion();
|
||||||
const tag = getPlatformTag();
|
return `${GITHUB_DOWNLOAD_BASE_URL}/chromium-v${v}/${getArchiveName()}`;
|
||||||
return `${GITHUB_DOWNLOAD_BASE_URL}/chromium-v${v}/cloakbrowser-${tag}.tar.gz`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEffectiveVersion(): string {
|
export function getEffectiveVersion(): string {
|
||||||
|
|||||||
+40
-20
@@ -19,6 +19,8 @@ import {
|
|||||||
GITHUB_DOWNLOAD_BASE_URL,
|
GITHUB_DOWNLOAD_BASE_URL,
|
||||||
WRAPPER_VERSION,
|
WRAPPER_VERSION,
|
||||||
checkPlatformAvailable,
|
checkPlatformAvailable,
|
||||||
|
getArchiveExt,
|
||||||
|
getArchiveName,
|
||||||
getBinaryDir,
|
getBinaryDir,
|
||||||
getBinaryPath,
|
getBinaryPath,
|
||||||
getCacheDir,
|
getCacheDir,
|
||||||
@@ -152,7 +154,7 @@ async function downloadAndExtract(version?: string): Promise<void> {
|
|||||||
// Download to temp file (atomic — no partial downloads in cache)
|
// Download to temp file (atomic — no partial downloads in cache)
|
||||||
const tmpPath = path.join(
|
const tmpPath = path.join(
|
||||||
path.dirname(binaryDir),
|
path.dirname(binaryDir),
|
||||||
`_download_${Date.now()}.tar.gz`
|
`_download_${Date.now()}${getArchiveExt()}`
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -194,7 +196,7 @@ async function downloadAndExtract(version?: string): Promise<void> {
|
|||||||
|
|
||||||
async function verifyDownloadChecksum(filePath: string, version?: string): Promise<void> {
|
async function verifyDownloadChecksum(filePath: string, version?: string): Promise<void> {
|
||||||
const checksums = await fetchChecksums(version);
|
const checksums = await fetchChecksums(version);
|
||||||
const tarballName = `cloakbrowser-${getPlatformTag()}.tar.gz`;
|
const tarballName = getArchiveName();
|
||||||
|
|
||||||
if (!checksums) {
|
if (!checksums) {
|
||||||
console.warn("[cloakbrowser] SHA256SUMS not available for this release — skipping checksum verification");
|
console.warn("[cloakbrowser] SHA256SUMS not available for this release — skipping checksum verification");
|
||||||
@@ -342,23 +344,11 @@ async function extractArchive(
|
|||||||
}
|
}
|
||||||
fs.mkdirSync(destDir, { recursive: true });
|
fs.mkdirSync(destDir, { recursive: true });
|
||||||
|
|
||||||
// Extract with tar — the 'tar' package handles symlink/traversal safety
|
if (archivePath.endsWith(".zip")) {
|
||||||
await tarExtract({
|
await extractZip(archivePath, destDir);
|
||||||
file: archivePath,
|
} else {
|
||||||
cwd: destDir,
|
await extractTar(archivePath, destDir);
|
||||||
// Security: strip leading path components and reject absolute paths
|
}
|
||||||
strip: 0,
|
|
||||||
filter: (entryPath: string) => {
|
|
||||||
// Reject absolute paths and path traversal
|
|
||||||
if (path.isAbsolute(entryPath) || entryPath.includes("..")) {
|
|
||||||
console.warn(
|
|
||||||
`[cloakbrowser] Skipping suspicious archive entry: ${entryPath}`
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Flatten single subdirectory if needed
|
// Flatten single subdirectory if needed
|
||||||
flattenSingleSubdir(destDir);
|
flattenSingleSubdir(destDir);
|
||||||
@@ -379,6 +369,36 @@ async function extractArchive(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function extractTar(archivePath: string, destDir: string): Promise<void> {
|
||||||
|
await tarExtract({
|
||||||
|
file: archivePath,
|
||||||
|
cwd: destDir,
|
||||||
|
strip: 0,
|
||||||
|
filter: (entryPath: string) => {
|
||||||
|
if (path.isAbsolute(entryPath) || entryPath.includes("..")) {
|
||||||
|
console.warn(
|
||||||
|
`[cloakbrowser] Skipping suspicious archive entry: ${entryPath}`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractZip(archivePath: string, destDir: string): Promise<void> {
|
||||||
|
const { execFileSync } = await import("node:child_process");
|
||||||
|
// Use system unzip — available on Windows (PowerShell), macOS, and Linux
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
execFileSync("powershell", [
|
||||||
|
"-NoProfile", "-Command",
|
||||||
|
`Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`,
|
||||||
|
], { timeout: 120_000 });
|
||||||
|
} else {
|
||||||
|
execFileSync("unzip", ["-o", archivePath, "-d", destDir], { timeout: 120_000 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If extraction created a single subdirectory, move its contents up.
|
* If extraction created a single subdirectory, move its contents up.
|
||||||
* Many tarballs wrap files in a top-level directory.
|
* Many tarballs wrap files in a top-level directory.
|
||||||
@@ -452,7 +472,7 @@ export async function getLatestChromiumVersion(): Promise<string | null> {
|
|||||||
draft: boolean;
|
draft: boolean;
|
||||||
assets: Array<{ name: string }>;
|
assets: Array<{ name: string }>;
|
||||||
}>;
|
}>;
|
||||||
const platformTarball = `cloakbrowser-${getPlatformTag()}.tar.gz`;
|
const platformTarball = getArchiveName();
|
||||||
for (const release of releases) {
|
for (const release of releases) {
|
||||||
if (release.tag_name.startsWith("chromium-v") && !release.draft) {
|
if (release.tag_name.startsWith("chromium-v") && !release.draft) {
|
||||||
const assetNames = new Set(
|
const assetNames = new Set(
|
||||||
|
|||||||
Reference in New Issue
Block a user