From 4b5d3ab0092ed8b17749431b3f284157ab63ff61 Mon Sep 17 00:00:00 2001 From: Eana Hufwe <eana@1a23.com> Date: Wed, 31 Jul 2024 23:08:32 +0000 Subject: [PATCH] fix: allow arbitrary BCP 47-compliant language via API --- packages/backend/src/services/note/create.ts | 9 ++++----- packages/client/src/components/MkPostForm.vue | 11 ++++++++--- packages/firefish-js/src/index.ts | 3 ++- packages/firefish-js/src/misc/langmap.ts | 4 ++++ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 4b21c67141..63c6239ccf 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -62,7 +62,7 @@ import { db } from "@/db/postgre.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { redisClient } from "@/db/redis.js"; import { Mutex } from "redis-semaphore"; -import { langmap } from "firefish-js"; +import { bcp47Pattern } from "firefish-js"; import Logger from "@/services/logger.js"; import { inspect } from "node:util"; import { toRustObject } from "@/prelude/undefined-to-null.js"; @@ -273,8 +273,7 @@ export default async ( data.text = data.text?.trim() ?? null; if (data.lang != null) { - if (!Object.keys(langmap).includes(data.lang.toLowerCase())) - throw new Error("invalid param"); + if (!bcp47Pattern.test(data.lang)) rej("Invalid language code"); data.lang = data.lang.toLowerCase(); } else { data.lang = null; @@ -317,7 +316,7 @@ export default async ( } if (!isDraft && data.visibility === "specified") { - if (data.visibleUsers == null) throw new Error("invalid param"); + if (data.visibleUsers == null) rej("invalid param"); for (const u of data.visibleUsers) { if (!mentionedUsers.some((x) => x.id === u.id)) { @@ -444,7 +443,7 @@ export default async ( // 未読通知を作成 if (data.visibility === "specified") { - if (data.visibleUsers == null) throw new Error("invalid param"); + if (data.visibleUsers == null) rej("invalid param"); for (const u of data.visibleUsers) { // ローカルユーザーのみ diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue index 88016cc25d..d408ca0e0b 100644 --- a/packages/client/src/components/MkPostForm.vue +++ b/packages/client/src/components/MkPostForm.vue @@ -864,9 +864,14 @@ function setLanguage() { active: true, action: () => {}, }); - } else { - // Unrecognized language, set to null - language.value = null; + } else if (language.value != null) { + // Unrecognized language, add it to the list as an ad-hoc language + actions.push({ + text: language.value, + danger: false, + active: true, + action: () => {}, + }); } const langs = Object.keys(langmap); diff --git a/packages/firefish-js/src/index.ts b/packages/firefish-js/src/index.ts index 957499f359..c134b28d81 100644 --- a/packages/firefish-js/src/index.ts +++ b/packages/firefish-js/src/index.ts @@ -13,7 +13,7 @@ import * as entities from "./entities.js"; import type * as SchemaTypes from "./misc/schema.js"; import * as Schema from "./misc/schema.js"; -import { langmap, type PostLanguage } from "./misc/langmap.js"; +import { langmap, bcp47Pattern, type PostLanguage } from "./misc/langmap.js"; export { type Endpoints, @@ -27,6 +27,7 @@ export { Schema, type SchemaTypes, langmap, + bcp47Pattern, type PostLanguage, api, entities, diff --git a/packages/firefish-js/src/misc/langmap.ts b/packages/firefish-js/src/misc/langmap.ts index 16d169d914..1ed301edff 100644 --- a/packages/firefish-js/src/misc/langmap.ts +++ b/packages/firefish-js/src/misc/langmap.ts @@ -378,5 +378,9 @@ export const iso639Regional = { }, }; +// Maverify a BCP 57 language tag (by ericP, CC BY-SA 4.0 https://stackoverflow.com/a/60899733) +export const bcp47Pattern = + /^((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?<extension>[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?<privateUse>x(-[A-Za-z0-9]{1,8})+))?)|(?<privateUse1>x(-[A-Za-z0-9]{1,8})+))$/i; + export const langmap = Object.assign({}, langmapNoRegion, iso639Regional); export type PostLanguage = keyof typeof langmap;