@@ -301,6 +305,23 @@ import {
badmanners.vore@gmail.com
+
+
+
+ GPG public key
+
+
- aspe:keyoxide.org:UWYBVFCBFXTVUF2U6FS6AYJHLU on Keyoxide
+
+ aspe:keyoxide.org:UWYBVFCBFXTVUF2U6FS6AYJHLU on Keyoxide
+
@@ -499,7 +522,7 @@ import {
class="u-key text-link group block w-full py-2 transition-colors motion-reduce:transition-none"
href="/ssh.pub"
aria-label="SSH public key"
- data-clipboard="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ3QAZd3E95gxef2kiXppWa/xhcwBtnKMZJaW6s4d7Tm Bad Manners "
+ data-clipboard={sshKey}
data-noun="SSH key"
>
{
+ if (!author) {
+ throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(copyright)})`);
+ }
+ if (typeof author !== "object" && typeof author !== "string") {
+ throw new Error(
+ `Invalid "${typeof author}" type for "author"${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(copyright)})`,
+ );
+ }
+ if (typeof author === "object" && !(author.name || author.url)) {
+ throw new Error(
+ `Missing both name and URL for "author" ${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(copyright)})`,
+ );
+ }
+ });
+ // Source must be a valid string or list of strings
+ const sources = [copyright.source].flat();
+ if (sources.length === 0) {
+ throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(copyright)})`);
+ }
+ sources.forEach((source) => {
+ if (!source) {
+ throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(copyright)})`);
+ }
+ if (typeof source !== "object" && typeof source !== "string") {
+ throw new Error(
+ `Invalid "${typeof source}" type for "source"${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(copyright)})`,
+ );
+ }
+ if (typeof source === "object" && !source.url) {
+ throw new Error(
+ `Missing URL for "source" ${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(copyright)})`,
+ );
+ }
+ });
+ // License must be a valid string or object or list
+ const licenses = [copyright.license].flat();
+ if (licenses.length === 0) {
+ throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(copyright)})`);
+ }
+ licenses.forEach((license) => {
+ if (!license) {
+ throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(copyright)})`);
+ }
+ if (typeof license !== "object" && typeof license !== "string") {
+ throw new Error(
+ `Invalid "${typeof license}" type for "license"${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(copyright)})`,
+ );
+ }
+ if (typeof license === "object" && !(license.name || license.url)) {
+ throw new Error(
+ `Missing both name and URL for "license" ${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(copyright)})`,
+ );
+ }
+ });
+}
+
+/**
+ * Verifies attributions and copyright according to the [Creative Commons recommended practices](https://wiki.creativecommons.org/wiki/Recommended_practices_for_attribution)
* @param copyright Unparsed TOML copyright information.
*/
function verifyAttributions(licenses: string) {
const { copyright, attributions } = parse(licenses);
- // Make sure each copyright and attribution follows the TASL format.
- // - T: title (or description)
- // - A: author
- // - S: source
- // - L: license
- // - other fields that have custom validation: type, notes, items
+ // Make sure each copyright and attribution follows the TASL format,
+ // and that other fields (type, notes, items) pass their custom validation.
[copyright, attributions].flat().forEach((value) => {
- // Title or description must be a valid string
- const title = value.title ?? value.description;
- if (typeof title !== "string" || !title) {
- throw new Error(`Missing "title" and/or "description" for attribution (${JSON.stringify(value)})`);
- }
- // Author must be a valid string or object or list
- const authors = [value.author].flat();
- if (authors.length === 0) {
- throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(value)})`);
- }
- authors.forEach((author) => {
- if (!author) {
- throw new Error(`Missing "author" for attribution "${title}" (${JSON.stringify(value)})`);
- }
- if (typeof author !== "object" && typeof author !== "string") {
- throw new Error(`Invalid "${typeof author}" type for "author"${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(value)})`);
- }
- if (typeof author === "object" && !(author.name || author.url)) {
- throw new Error(`Missing both name and URL for "author" ${JSON.stringify(author)} for attribution "${title}" (${JSON.stringify(value)})`);
- }
- });
- // Source must be a valid string or list of strings
- const sources = [value.source].flat();
- if (sources.length === 0) {
- throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(value)})`);
- }
- sources.forEach((source) => {
- if (!source) {
- throw new Error(`Missing "source" for attribution "${title}" (${JSON.stringify(value)})`);
- }
- if (typeof source !== "object" && typeof source !== "string") {
- throw new Error(`Invalid "${typeof source}" type for "source"${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(value)})`);
- }
- if (typeof source === "object" && !(source.url)) {
- throw new Error(`Missing URL for "source" ${JSON.stringify(source)} for attribution "${title}" (${JSON.stringify(value)})`);
- }
- });
- // License must be a valid string or object or list
- const licenses = [value.license].flat();
- if (licenses.length === 0) {
- throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(value)})`);
- }
- licenses.forEach((license) => {
- if (!license) {
- throw new Error(`Missing "license" for attribution "${title}" (${JSON.stringify(value)})`);
- }
- if (typeof license !== "object" && typeof license !== "string") {
- throw new Error(`Invalid "${typeof license}" type for "license"${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(value)})`);
- }
- if (typeof license === "object" && !(license.name || license.url)) {
- throw new Error(`Missing both name and URL for "license" ${JSON.stringify(license)} for attribution "${title}" (${JSON.stringify(value)})`);
- }
- });
+ // Validate TASL
+ validateTASL(value);
+ const title = copyright.title ?? copyright.description;
// Validate extra optional fields
// 1. Type must be a valid string
if (typeof value.type !== "string") {
@@ -80,13 +97,17 @@ function verifyAttributions(licenses: string) {
if ("items" in value) {
const items = value.items;
if (!Array.isArray(items)) {
- throw new Error(`Invalid non-array "items" ${JSON.stringify(items)} for attribution "${title}" (${JSON.stringify(value)})`);
+ throw new Error(
+ `Invalid non-array "items" ${JSON.stringify(items)} for attribution "${title}" (${JSON.stringify(value)})`,
+ );
}
items.forEach((item) => {
if (!item) {
- throw new Error(`Invalid item ${JSON.stringify} in "items" for attribution "${title}" (${JSON.stringify(value)})`);
+ throw new Error(
+ `Invalid item ${JSON.stringify} in "items" for attribution "${title}" (${JSON.stringify(value)})`,
+ );
}
- })
+ });
}
// 3. Type must be a valid string
if ("notes" in value) {
@@ -106,18 +127,33 @@ function verifyAttributions(licenses: string) {
if ("additional" in copyright) {
const additionals = copyright.additional;
if (!Array.isArray(additionals)) {
- throw new Error(`Invalid non-array "additional" ${JSON.stringify(additionals)} for copyright (${JSON.stringify(copyright)})`);
+ throw new Error(
+ `Invalid non-array "additional" ${JSON.stringify(additionals)} for copyright (${JSON.stringify(copyright)})`,
+ );
}
additionals.forEach((additional) => {
if (typeof additional.notes !== "string" || !additional.notes) {
throw new Error(`Invalid "notes" for additional copyright (${JSON.stringify(additional)})`);
}
- })
+ if (typeof additional.type !== "string" || !additional.type) {
+ throw new Error(`Invalid "type" for additional copyright (${JSON.stringify(additional)})`);
+ }
+ // Check TASL + date if title or description is present
+ if (additional.title || additional.description) {
+ validateTASL(additional);
+ if (typeof additional.date !== "string") {
+ throw new Error(`Invalid "date" for additional (${JSON.stringify(additional)})`);
+ }
+ if (!additional.date) {
+ throw new Error(`Missing "date" for additional (${JSON.stringify(additional)})`);
+ }
+ }
+ });
}
}
export const GET: APIRoute = async () => {
const licenses = await readFile("./src/data/licenses.toml", { encoding: "utf-8" });
verifyAttributions(licenses);
- return new Response(licenses, { headers: { "Content-Type": "application/toml; charset=utf-8" } })
+ return new Response(licenses, { headers: { "Content-Type": "application/toml; charset=utf-8" } });
};