Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions macos/assets/theme-package-validator.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const KEY_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const CONTROL_PATTERN = /[\u0000-\u001f\u007f]/u;
const PROVENANCE_CONTROL_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
const COLOR_PATTERN = /^(#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?|#[0-9a-fA-F]{3,4}|rgb\(\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*\)|rgba\(\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*,\s*(0|1|1\.0|0?\.[0-9]{1,6})\s*\))$/;
const RFC3339_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,9})?(?:Z|[+-][0-9]{2}:[0-9]{2})$/;
const RFC3339_PATTERN = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.([0-9]{1,9}))?(?:Z|([+-])([0-9]{2}):([0-9]{2}))$/;
const OPEN_FLAGS = fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0);
const decoder = new TextDecoder("utf-8", { fatal: true });
const scriptPath = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -265,7 +265,26 @@ async function sourceFileNames(root) {

function validateTimestamp(value) {
assertString(value, "manifest.createdAt", { min: 1, max: 40, pattern: RFC3339_PATTERN, controls: null });
if (!Number.isFinite(Date.parse(value))) fail("manifest.createdAt is not a valid date-time");
const match = RFC3339_PATTERN.exec(value);
if (!match) fail("manifest.createdAt is not a valid date-time");

const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[3]);
const hour = Number(match[4]);
const minute = Number(match[5]);
const second = Number(match[6]);
const offsetHour = match[9] === undefined ? 0 : Number(match[9]);
const offsetMinute = match[10] === undefined ? 0 : Number(match[10]);
const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
const daysInMonth = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const calendarValid = month >= 1 && month <= 12
&& day >= 1 && day <= (daysInMonth[month - 1] ?? 0)
&& hour <= 23 && minute <= 59 && second <= 59
&& offsetHour <= 23 && offsetMinute <= 59;
if (!calendarValid || !Number.isFinite(Date.parse(value))) {
fail("manifest.createdAt is not a valid date-time");
}
}

function validateOfficialTheme(value) {
Expand Down
18 changes: 18 additions & 0 deletions macos/tests/theme-package-validator.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,24 @@ try {
"theme.json",
]);

const impossibleTimestamp = await makeOfficial("official-impossible-timestamp", {
mutateManifest: (manifest) => { manifest.createdAt = "2026-02-30T00:00:00Z"; },
});
await expectRejected(
impossibleTimestamp.source,
"macos",
/createdAt is not a valid date-time/,
"impossible-timestamp",
);

const leapTimestamp = await makeOfficial("official-valid-leap-timestamp", {
mutateManifest: (manifest) => {
manifest.createdAt = "2024-02-29T23:59:59.123456789+05:30";
},
});
const leapResult = await validate(leapTimestamp.source, "windows", "valid-leap-timestamp");
assert.equal(leapResult.output.format, "official");

const legacyOfficial = await makeOfficial("legacy-official-no-css", { css: false });
await expectRejected(
legacyOfficial.source,
Expand Down
23 changes: 21 additions & 2 deletions runtime/theme-package-validator.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const KEY_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const CONTROL_PATTERN = /[\u0000-\u001f\u007f]/u;
const PROVENANCE_CONTROL_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
const COLOR_PATTERN = /^(#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?|#[0-9a-fA-F]{3,4}|rgb\(\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*\)|rgba\(\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*,\s*(0|1|1\.0|0?\.[0-9]{1,6})\s*\))$/;
const RFC3339_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,9})?(?:Z|[+-][0-9]{2}:[0-9]{2})$/;
const RFC3339_PATTERN = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.([0-9]{1,9}))?(?:Z|([+-])([0-9]{2}):([0-9]{2}))$/;
const OPEN_FLAGS = fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0);
const decoder = new TextDecoder("utf-8", { fatal: true });
const scriptPath = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -265,7 +265,26 @@ async function sourceFileNames(root) {

function validateTimestamp(value) {
assertString(value, "manifest.createdAt", { min: 1, max: 40, pattern: RFC3339_PATTERN, controls: null });
if (!Number.isFinite(Date.parse(value))) fail("manifest.createdAt is not a valid date-time");
const match = RFC3339_PATTERN.exec(value);
if (!match) fail("manifest.createdAt is not a valid date-time");

const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[3]);
const hour = Number(match[4]);
const minute = Number(match[5]);
const second = Number(match[6]);
const offsetHour = match[9] === undefined ? 0 : Number(match[9]);
const offsetMinute = match[10] === undefined ? 0 : Number(match[10]);
const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
const daysInMonth = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const calendarValid = month >= 1 && month <= 12
&& day >= 1 && day <= (daysInMonth[month - 1] ?? 0)
&& hour <= 23 && minute <= 59 && second <= 59
&& offsetHour <= 23 && offsetMinute <= 59;
if (!calendarValid || !Number.isFinite(Date.parse(value))) {
fail("manifest.createdAt is not a valid date-time");
}
}

function validateOfficialTheme(value) {
Expand Down
23 changes: 21 additions & 2 deletions windows/assets/theme-package-validator.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const KEY_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const CONTROL_PATTERN = /[\u0000-\u001f\u007f]/u;
const PROVENANCE_CONTROL_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
const COLOR_PATTERN = /^(#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?|#[0-9a-fA-F]{3,4}|rgb\(\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*\)|rgba\(\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*,\s*(0|1|1\.0|0?\.[0-9]{1,6})\s*\))$/;
const RFC3339_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,9})?(?:Z|[+-][0-9]{2}:[0-9]{2})$/;
const RFC3339_PATTERN = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.([0-9]{1,9}))?(?:Z|([+-])([0-9]{2}):([0-9]{2}))$/;
const OPEN_FLAGS = fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0);
const decoder = new TextDecoder("utf-8", { fatal: true });
const scriptPath = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -265,7 +265,26 @@ async function sourceFileNames(root) {

function validateTimestamp(value) {
assertString(value, "manifest.createdAt", { min: 1, max: 40, pattern: RFC3339_PATTERN, controls: null });
if (!Number.isFinite(Date.parse(value))) fail("manifest.createdAt is not a valid date-time");
const match = RFC3339_PATTERN.exec(value);
if (!match) fail("manifest.createdAt is not a valid date-time");

const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[3]);
const hour = Number(match[4]);
const minute = Number(match[5]);
const second = Number(match[6]);
const offsetHour = match[9] === undefined ? 0 : Number(match[9]);
const offsetMinute = match[10] === undefined ? 0 : Number(match[10]);
const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
const daysInMonth = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const calendarValid = month >= 1 && month <= 12
&& day >= 1 && day <= (daysInMonth[month - 1] ?? 0)
&& hour <= 23 && minute <= 59 && second <= 59
&& offsetHour <= 23 && offsetMinute <= 59;
if (!calendarValid || !Number.isFinite(Date.parse(value))) {
fail("manifest.createdAt is not a valid date-time");
}
}

function validateOfficialTheme(value) {
Expand Down
Loading