mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-10-18 23:07:13 -05:00
feat(info): report macOS persona font completeness
Add a Mac fonts count to `cloakbrowser info` next to the existing Windows font check, in the Python, JS, and .NET wrappers. Counts the macOS core font set via fc-list on Linux and prints Mac fonts: n/20 with a conditional nudge when incomplete. Tests added in all three wrappers.
This commit is contained in:
11 files changed
+234
-8
No files matched your search
@@ -306,6 +306,7 @@ def _collect_diagnostics(quick: bool, proxy: str | None = None) -> dict:
|
||||
# Omitted entirely off Linux, where it carries no signal.
|
||||
if platform.system() == "Linux":
|
||||
from .browser import (
|
||||
_MAC_FONT_TELLS,
|
||||
_OFFICE_FONT_TELLS,
|
||||
_WINDOWS_FONT_TELLS,
|
||||
_count_fonts_present,
|
||||
@@ -313,11 +314,16 @@ def _collect_diagnostics(quick: bool, proxy: str | None = None) -> dict:
|
||||
|
||||
# Strict count, not "any one present" — real font installs are atomic
|
||||
# (you have the whole pack or none), so report how complete the set is.
|
||||
# Both Windows and macOS sets are counted unconditionally: `info` has no
|
||||
# persona flag, so we report each raw count and let the user read the one
|
||||
# for the persona they spoof.
|
||||
win_n = _count_fonts_present(_WINDOWS_FONT_TELLS)
|
||||
office_n = _count_fonts_present(_OFFICE_FONT_TELLS)
|
||||
mac_n = _count_fonts_present(_MAC_FONT_TELLS)
|
||||
diag["fonts"] = {
|
||||
"windows": None if win_n is None else [win_n, len(_WINDOWS_FONT_TELLS)],
|
||||
"office": None if office_n is None else [office_n, len(_OFFICE_FONT_TELLS)],
|
||||
"macos": None if mac_n is None else [mac_n, len(_MAC_FONT_TELLS)],
|
||||
}
|
||||
|
||||
diag["license"] = license_info
|
||||
@@ -477,6 +483,17 @@ def _print_diagnostics(diag: dict) -> None:
|
||||
# persona (~53% of real machines have none), so no install nudge.
|
||||
verdict = "ok" if n == total else "absent" if n == 0 else "partial"
|
||||
print(f"Office fonts: {verdict} ({n}/{total})")
|
||||
mac = diag["fonts"].get("macos")
|
||||
if mac is None:
|
||||
print("Mac fonts: unknown (fc-list unavailable)")
|
||||
else:
|
||||
n, total = mac
|
||||
verdict = "ok" if n == total else "missing" if n == 0 else "partial"
|
||||
print(f"Mac fonts: {verdict} ({n}/{total})")
|
||||
if n < total:
|
||||
# Phrased conditionally: some Mac fonts also ship on a Windows box,
|
||||
# so a partial count is expected unless macOS is the intended persona.
|
||||
print(f" {ARROW} if spoofing macOS on this host, copy the real Mac fonts (Helvetica Neue, Menlo, SF/Apple system fonts)")
|
||||
|
||||
lic = diag["license"]
|
||||
tier = lic["tier"]
|
||||
|
||||
@@ -1520,6 +1520,34 @@ _OFFICE_FONT_TELLS = (
|
||||
"Bookman Old Style",
|
||||
)
|
||||
|
||||
# macOS OS fonts — the core set present on essentially every real Mac. Their
|
||||
# absence on a macOS-spoofing Linux host degrades results, just as a Windows-
|
||||
# spoofing host needs the Windows set above. Several entries (Arial, Georgia,
|
||||
# Times New Roman, Courier New, Tahoma) also ship on a Windows-provisioned box,
|
||||
# so a partial count on a non-Mac host is normal, not a fault.
|
||||
_MAC_FONT_TELLS = (
|
||||
"Apple Color Emoji",
|
||||
"Arial",
|
||||
"Arial Narrow",
|
||||
"Arial Unicode MS",
|
||||
"Comic Sans MS",
|
||||
"Courier",
|
||||
"Courier New",
|
||||
"Georgia",
|
||||
"Gill Sans",
|
||||
"Helvetica",
|
||||
"Helvetica Neue",
|
||||
"Impact",
|
||||
"Menlo",
|
||||
"Microsoft Sans Serif",
|
||||
"Monaco",
|
||||
"Tahoma",
|
||||
"Times New Roman",
|
||||
"Trebuchet MS",
|
||||
"Webdings",
|
||||
"Wingdings",
|
||||
)
|
||||
|
||||
_font_warning_checked = False
|
||||
|
||||
|
||||
|
||||
@@ -206,6 +206,20 @@ static void PrintDiagnostics(Dictionary<string, object?> diag)
|
||||
string verdict = n == total ? "ok" : n == 0 ? "absent" : "partial";
|
||||
Console.WriteLine($"Office fonts: {verdict} ({n}/{total})");
|
||||
}
|
||||
if (fonts.TryGetValue("macos", out var macObj) && macObj is int[] mac)
|
||||
{
|
||||
int n = mac[0], total = mac[1];
|
||||
string verdict = n == total ? "ok" : n == 0 ? "missing" : "partial";
|
||||
Console.WriteLine($"Mac fonts: {verdict} ({n}/{total})");
|
||||
if (n < total)
|
||||
// Phrased conditionally: some Mac fonts also ship on a Windows box,
|
||||
// so a partial count is expected unless macOS is the intended persona.
|
||||
Console.WriteLine(" → if spoofing macOS on this host, copy the real Mac fonts (Helvetica Neue, Menlo, SF/Apple system fonts)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Mac fonts: unknown (fc-list unavailable)");
|
||||
}
|
||||
}
|
||||
|
||||
var lic = (Dictionary<string, object?>)diag["license"]!;
|
||||
|
||||
@@ -480,6 +480,20 @@ public static class CloakLauncher
|
||||
"Monotype Corsiva", "Bookman Old Style",
|
||||
};
|
||||
|
||||
// macOS OS fonts — the core set present on essentially every real Mac. Their
|
||||
// absence on a macOS-spoofing Linux host degrades results, just as a Windows-
|
||||
// spoofing host needs the Windows set above. Several entries (Arial, Georgia,
|
||||
// Times New Roman, Courier New, Tahoma) also ship on a Windows box, so a
|
||||
// partial count off-Mac is normal.
|
||||
internal static readonly string[] MacFontTells =
|
||||
{
|
||||
"Apple Color Emoji", "Arial", "Arial Narrow", "Arial Unicode MS",
|
||||
"Comic Sans MS", "Courier", "Courier New", "Georgia", "Gill Sans",
|
||||
"Helvetica", "Helvetica Neue", "Impact", "Menlo", "Microsoft Sans Serif",
|
||||
"Monaco", "Tahoma", "Times New Roman", "Trebuchet MS", "Webdings",
|
||||
"Wingdings",
|
||||
};
|
||||
|
||||
internal static bool _fontWarningChecked;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -79,18 +79,22 @@ internal static class Diagnostics
|
||||
diag["launch"] = launch;
|
||||
}
|
||||
|
||||
// Windows-font probe — only meaningful on a Linux host spoofing Windows.
|
||||
// Omitted entirely off Linux, where it carries no signal.
|
||||
// Windows/macOS-font probe — only meaningful on a Linux host spoofing
|
||||
// another OS. Omitted entirely off Linux, where it carries no signal.
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
// Strict count, not "any one present" — real font installs are atomic
|
||||
// (you have the whole pack or none), so report how complete the set is.
|
||||
// Both sets are counted unconditionally: `info` has no persona flag, so
|
||||
// we report each raw count and let the user read the one they spoof.
|
||||
int? winN = CloakLauncher.CountFontsPresent(CloakLauncher.WindowsFontTells);
|
||||
int? officeN = CloakLauncher.CountFontsPresent(CloakLauncher.OfficeFontTells);
|
||||
int? macN = CloakLauncher.CountFontsPresent(CloakLauncher.MacFontTells);
|
||||
diag["fonts"] = new Dictionary<string, object?>
|
||||
{
|
||||
["windows"] = winN is null ? null : new[] { winN.Value, CloakLauncher.WindowsFontTells.Length },
|
||||
["office"] = officeN is null ? null : new[] { officeN.Value, CloakLauncher.OfficeFontTells.Length },
|
||||
["macos"] = macN is null ? null : new[] { macN.Value, CloakLauncher.MacFontTells.Length },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -192,4 +192,39 @@ public class FontWarningTests
|
||||
try { Directory.Delete(tmp, recursive: true); } catch { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MacFontTells_is_the_20_font_mac_core_set()
|
||||
{
|
||||
Assert.Equal(20, CloakLauncher.MacFontTells.Length);
|
||||
Assert.Contains("Helvetica Neue", CloakLauncher.MacFontTells);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CountFontsPresent_counts_the_mac_set_from_fc_list()
|
||||
{
|
||||
// Shim an "fc-list" on PATH that lists only 2 of the mac tells (POSIX only).
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
|
||||
|
||||
var dir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(dir);
|
||||
var shim = Path.Combine(dir, "fc-list");
|
||||
File.WriteAllText(shim, "#!/bin/sh\necho '/x/Menlo.ttc: Menlo:style=Regular'\necho '/x/Monaco.ttf: Monaco:style=Regular'\n");
|
||||
using (var chmod = Process.Start(new ProcessStartInfo("chmod", $"+x \"{shim}\"") { UseShellExecute = false }))
|
||||
{
|
||||
chmod!.WaitForExit();
|
||||
}
|
||||
|
||||
var prevPath = Environment.GetEnvironmentVariable("PATH");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("PATH", dir + Path.PathSeparator + prevPath);
|
||||
Assert.Equal(2, CloakLauncher.CountFontsPresent(CloakLauncher.MacFontTells));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("PATH", prevPath);
|
||||
try { Directory.Delete(dir, recursive: true); } catch { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-3
@@ -24,7 +24,7 @@ import {
|
||||
CHROMIUM_VERSION,
|
||||
WRAPPER_VERSION,
|
||||
} from "./config.js";
|
||||
import { countFontsPresent, WINDOWS_FONT_TELLS, OFFICE_FONT_TELLS } from "./fonts.js";
|
||||
import { countFontsPresent, WINDOWS_FONT_TELLS, OFFICE_FONT_TELLS, MACOS_FONT_TELLS } from "./fonts.js";
|
||||
import { resolveProxyGeo } from "./geoip.js";
|
||||
import { resolveLicenseKey, validateLicense, getProLatestRelease, getSessionSeats, type LicenseInfo } from "./license.js";
|
||||
import { execFileSync, spawn } from "node:child_process";
|
||||
@@ -268,16 +268,20 @@ export async function collectDiagnostics(
|
||||
if (!ok) diag.launch.missing_libs = missingSharedLibs(binPath);
|
||||
}
|
||||
|
||||
// Windows-font probe — only meaningful on a Linux host spoofing Windows.
|
||||
// Omitted entirely off Linux, where it carries no signal.
|
||||
// Windows/macOS-font probe — only meaningful on a Linux host spoofing another
|
||||
// OS. Omitted entirely off Linux, where it carries no signal.
|
||||
if (os.platform() === "linux") {
|
||||
// Strict count, not "any one present" — real font installs are atomic
|
||||
// (you have the whole pack or none), so report how complete the set is.
|
||||
// Both sets are counted unconditionally: `info` has no persona flag, so we
|
||||
// report each raw count and let the user read the one for the persona they spoof.
|
||||
const winN = countFontsPresent(WINDOWS_FONT_TELLS);
|
||||
const officeN = countFontsPresent(OFFICE_FONT_TELLS);
|
||||
const macN = countFontsPresent(MACOS_FONT_TELLS);
|
||||
diag.fonts = {
|
||||
windows: winN === null ? null : [winN, WINDOWS_FONT_TELLS.length],
|
||||
office: officeN === null ? null : [officeN, OFFICE_FONT_TELLS.length],
|
||||
macos: macN === null ? null : [macN, MACOS_FONT_TELLS.length],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -436,6 +440,19 @@ function printDiagnostics(diag: Record<string, any>): void {
|
||||
const verdict = n === total ? "ok" : n === 0 ? "absent" : "partial";
|
||||
console.log(`Office fonts: ${verdict} (${n}/${total})`);
|
||||
}
|
||||
const mac = diag.fonts.macos;
|
||||
if (mac === undefined || mac === null) {
|
||||
console.log("Mac fonts: unknown (fc-list unavailable)");
|
||||
} else {
|
||||
const [n, total] = mac;
|
||||
const verdict = n === total ? "ok" : n === 0 ? "missing" : "partial";
|
||||
console.log(`Mac fonts: ${verdict} (${n}/${total})`);
|
||||
if (n < total) {
|
||||
// Phrased conditionally: some Mac fonts also ship on a Windows box, so a
|
||||
// partial count is expected unless macOS is the intended persona.
|
||||
console.log(" → if spoofing macOS on this host, copy the real Mac fonts (Helvetica Neue, Menlo, SF/Apple system fonts)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lic = diag.license;
|
||||
|
||||
@@ -45,6 +45,34 @@ export const OFFICE_FONT_TELLS = [
|
||||
"Bookman Old Style",
|
||||
];
|
||||
|
||||
// macOS OS fonts — the core set present on essentially every real Mac. Their
|
||||
// absence on a macOS-spoofing Linux host degrades results, just as a Windows-
|
||||
// spoofing host needs the Windows set above. Several entries (Arial, Georgia,
|
||||
// Times New Roman, Courier New, Tahoma) also ship on a Windows box, so a partial
|
||||
// count off-Mac is normal.
|
||||
export const MACOS_FONT_TELLS = [
|
||||
"Apple Color Emoji",
|
||||
"Arial",
|
||||
"Arial Narrow",
|
||||
"Arial Unicode MS",
|
||||
"Comic Sans MS",
|
||||
"Courier",
|
||||
"Courier New",
|
||||
"Georgia",
|
||||
"Gill Sans",
|
||||
"Helvetica",
|
||||
"Helvetica Neue",
|
||||
"Impact",
|
||||
"Menlo",
|
||||
"Microsoft Sans Serif",
|
||||
"Monaco",
|
||||
"Tahoma",
|
||||
"Times New Roman",
|
||||
"Trebuchet MS",
|
||||
"Webdings",
|
||||
"Wingdings",
|
||||
];
|
||||
|
||||
let fontWarningChecked = false;
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,9 +72,14 @@ describe("collectDiagnostics", () => {
|
||||
const diag = (await collectDiagnostics(true)) as Record<string, any>;
|
||||
|
||||
expect(diag.binary).toBeDefined();
|
||||
// fonts section only present on Linux
|
||||
if (os.platform() === "linux") expect(diag.fonts.windows).toBeDefined();
|
||||
else expect(diag.fonts).toBeUndefined();
|
||||
// fonts section only present on Linux — reports both the Windows and macOS
|
||||
// persona sets so a user can read the count for whichever they spoof.
|
||||
if (os.platform() === "linux") {
|
||||
expect(diag.fonts.windows).toBeDefined();
|
||||
expect(diag.fonts.macos).toBeDefined();
|
||||
} else {
|
||||
expect(diag.fonts).toBeUndefined();
|
||||
}
|
||||
expect(typeof diag.geoip.db_present).toBe("boolean");
|
||||
expect(Object.keys(diag.modules).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -138,3 +138,17 @@ describe("maybeWarnWindowsFonts", () => {
|
||||
expect(String(warn.mock.calls[0][0])).toContain(MSG);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MACOS_FONT_TELLS", () => {
|
||||
it("holds the 20-font mac core set and counts via fc-list", async () => {
|
||||
const { MACOS_FONT_TELLS, countFontsPresent } = await import("../src/fonts.js");
|
||||
expect(MACOS_FONT_TELLS.length).toBe(20);
|
||||
expect(MACOS_FONT_TELLS).toContain("Helvetica Neue");
|
||||
const cp = await import("node:child_process");
|
||||
// fc-list listing with only 2 of the mac tells present.
|
||||
vi.mocked(cp.execFileSync).mockReturnValue(
|
||||
"/x/Menlo.ttc: Menlo:style=Regular\n/x/Monaco.ttf: Monaco:style=Regular" as any,
|
||||
);
|
||||
expect(countFontsPresent(MACOS_FONT_TELLS as string[])).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -306,6 +306,56 @@ def test_free_license_shows_upgrade_hint(capsys):
|
||||
assert "cloakbrowser.dev" in out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Font probe — the Linux-only Windows/macOS-persona font check. Forces the Linux
|
||||
# branch and mocks fc-list so the count is deterministic on any host.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _fonts_linux(mac_present):
|
||||
"""Patch context: force the Linux font branch and stub fc-list so the Windows
|
||||
and Office sets read fully present while the mac set reports `mac_present`.
|
||||
"""
|
||||
from cloakbrowser.browser import _MAC_FONT_TELLS
|
||||
|
||||
def fake_count(tells):
|
||||
return mac_present if tells is _MAC_FONT_TELLS else len(tells)
|
||||
|
||||
return (
|
||||
patch("cloakbrowser.__main__.platform.system", return_value="Linux"),
|
||||
patch("cloakbrowser.browser._count_fonts_present", side_effect=fake_count),
|
||||
)
|
||||
|
||||
|
||||
def test_info_reports_partial_mac_fonts_with_conditional_nudge(capsys):
|
||||
# 5/20 = the Windows-box overlap (Arial, Georgia, Times New Roman, ...).
|
||||
p_platform, p_count = _fonts_linux(5)
|
||||
with p_platform, p_count:
|
||||
_run(Namespace(quick=True, json=False))
|
||||
out = capsys.readouterr().out
|
||||
assert "Mac fonts: partial (5/20)" in out
|
||||
assert "if spoofing macOS on this host" in out
|
||||
|
||||
|
||||
def test_info_full_mac_fonts_has_no_nudge(capsys):
|
||||
from cloakbrowser.browser import _MAC_FONT_TELLS
|
||||
|
||||
p_platform, p_count = _fonts_linux(len(_MAC_FONT_TELLS))
|
||||
with p_platform, p_count:
|
||||
_run(Namespace(quick=True, json=False))
|
||||
out = capsys.readouterr().out
|
||||
assert "Mac fonts: ok (20/20)" in out
|
||||
assert "if spoofing macOS on this host" not in out
|
||||
|
||||
|
||||
def test_info_json_includes_mac_font_count_on_linux(capsys):
|
||||
p_platform, p_count = _fonts_linux(5)
|
||||
with p_platform, p_count:
|
||||
_run(Namespace(quick=True, json=True))
|
||||
data = json.loads(capsys.readouterr().out)
|
||||
assert data["fonts"]["macos"] == [5, 20]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Launch test — exercises the real subprocess path (not --quick) against a stub
|
||||
# executable, so the launch-test code is actually covered by CI.
|
||||
|
||||
Reference in new issue
Block a user