diff --git a/packages/client/src/components/MkUrlPreview.vue b/packages/client/src/components/MkUrlPreview.vue
index 9241c49c36..3db512e148 100644
--- a/packages/client/src/components/MkUrlPreview.vue
+++ b/packages/client/src/components/MkUrlPreview.vue
@@ -103,6 +103,8 @@ import { onUnmounted, ref } from "vue";
import { url as local, lang } from "@/config";
import { i18n } from "@/i18n";
import { defaultStore } from "@/store";
+import { parseUri, parsedUriToString } from '@/scripts/parse-uri'
+import { SCHEMA_BROWSERSAFE } from "../const";
const props = withDefaults(
defineProps<{
@@ -134,30 +136,30 @@ let tweetExpanded = ref(props.detail);
const embedId = `embed${Math.random().toString().replace(/\D/, "")}`;
let tweetHeight = ref(150);
-const requestUrl = new URL(props.url);
-if (!["http:", "https:", "gopher:", "gemini:", "matrix:", "ipfs:", "ipns:", "finger:"].includes(requestUrl.protocol) && !requestUrl.protocol.startsWith("web+"))
+const requestUrl = parseUri(props.url);
+if (!SCHEMA_BROWSERSAFE.includes(requestUrl.scheme))
throw new Error("invalid url");
if (
- ["twitter.com", "mobile.twitter.com", "x.com"].includes(requestUrl.hostname)
+ ["twitter.com", "mobile.twitter.com", "x.com"].includes(requestUrl.authority ?? "")
) {
- const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
+ const m = (requestUrl.path ?? "").match(/^\/.+\/status(?:es)?\/(\d+)/);
if (m) tweetId.value = m[1];
}
if (
- requestUrl.hostname === "music.youtube.com" &&
- requestUrl.pathname.match("^/(?:watch|channel)")
+ requestUrl.authority === "music.youtube.com" &&
+ (requestUrl.path ?? "").match("^/(?:watch|channel)")
) {
- requestUrl.hostname = "www.youtube.com";
+ requestUrl.authority = "www.youtube.com";
}
const requestLang = (lang || "ja-JP").replace("ja-KS", "ja-JP");
-requestUrl.hash = "";
+requestUrl.fragment = "";
fetch(
- `/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`,
+ `/url?url=${encodeURIComponent(parsedUriToString(requestUrl))}&lang=${requestLang}`,
).then((res) => {
res.json().then((info) => {
if (info.url == null) return;
diff --git a/packages/client/src/components/global/MkUrl.vue b/packages/client/src/components/global/MkUrl.vue
index e44e980054..387e610bbf 100644
--- a/packages/client/src/components/global/MkUrl.vue
+++ b/packages/client/src/components/global/MkUrl.vue
@@ -11,7 +11,7 @@
@click.stop
>
- {{ schema }}{{ hostname.trim() != '' ? '//' : '' }}
+ {{ schema }}{{ hostname.trim() != '' ? '://' : '' }}
{{ hostname.trim() != '' ? hostname.trim() : pathname }}
@@ -38,6 +38,7 @@ import * as os from "@/os";
import { useTooltip } from "@/scripts/use-tooltip";
import { safeURIDecode } from "@/scripts/safe-uri-decode";
import { parseUri } from "@/scripts/parse-uri"
+import { SCHEMA_BROWSERSAFE } from "../../const";
const props = defineProps<{
url: string;
@@ -46,7 +47,10 @@ const props = defineProps<{
const self = props.url.startsWith(local);
const url = parseUri(props.url);
-if (!["http", "https", "gopher", "gemini", "matrix", "ipfs", "ipns", "finger"].includes(url.scheme) && !url.scheme.startsWith("web+"))
+console.log(url);
+console.log(props.url);
+console.log(url.scheme);
+if (!SCHEMA_BROWSERSAFE.includes(url.scheme))
throw new Error("invalid url");
const el = ref();
diff --git a/packages/client/src/const.ts b/packages/client/src/const.ts
index a73e09718e..7bf8865e23 100644
--- a/packages/client/src/const.ts
+++ b/packages/client/src/const.ts
@@ -38,6 +38,19 @@ export const FILE_TYPE_BROWSERSAFE = [
"audio/x-flac",
"audio/vnd.wave",
];
+
+export const SCHEMA_BROWSERSAFE = [
+ "http",
+ "https",
+ "gopher",
+ "gemini",
+ "matrix",
+ "ipfs",
+ "ipns",
+ "icmp",
+ "finger"
+];
+
/*
https://github.com/sindresorhus/file-type/blob/main/supported.js
https://github.com/sindresorhus/file-type/blob/main/core.js
diff --git a/packages/client/src/scripts/parse-uri.ts b/packages/client/src/scripts/parse-uri.ts
index 3408c5f078..a0a9216a77 100644
--- a/packages/client/src/scripts/parse-uri.ts
+++ b/packages/client/src/scripts/parse-uri.ts
@@ -1,10 +1,12 @@
-export function parseUri(uri: string): {
+type URI = {
scheme: string;
authority: string | null;
path: string | null;
query: string | null;
fragment: string | null;
-} {
+}
+
+export function parseUri(uri: string): URI {
const urlParts = uri.match(
/^([\w+]+):(\/\/)?([^/?#]*)(\/([^?#]*))?(\?([^#]*))?(#(.*))?$/,
);
@@ -24,3 +26,25 @@ export function parseUri(uri: string): {
fragment: fragment || null,
};
}
+
+export function parsedUriToString(uri: URI): string {
+ let result = `${uri.scheme}:`;
+
+ if (uri.authority) {
+ result += `//${uri.authority}`;
+ }
+
+ if (uri.path) {
+ result += uri.path;
+ }
+
+ if (uri.query) {
+ result += `?${uri.query}`;
+ }
+
+ if (uri.fragment) {
+ result += `#${uri.fragment}`;
+ }
+
+ return result;
+}
diff --git a/packages/ffm-js/src/internal/parser.ts b/packages/ffm-js/src/internal/parser.ts
index 3dc186c4cb..4775003d61 100644
--- a/packages/ffm-js/src/internal/parser.ts
+++ b/packages/ffm-js/src/internal/parser.ts
@@ -674,7 +674,7 @@ export const language = P.createLanguage({
]));
const parser = P.seq([
notLinkLabel,
- P.regexp(/((https?)|(gemini)|(gopher)|(matrix)|(ipns)|(ipfs)|(finger)|(web\+\w+)):\/\//),
+ P.regexp(/(?:https?|gemini|gopher|matrix|ipns|ipfs|icmp|finger|cool|web\+\w+):\/\//),
innerItem.many(1).text(),
]);
return new P.Parser((input, index, state) => {
@@ -706,7 +706,7 @@ export const language = P.createLanguage({
const parser = P.seq([
notLinkLabel,
open,
- P.regexp(/((https?)|(gemini)|(gopher)|(matrix)|(ipns)|(ipfs)|(finger)|(web\+\w+)):\/\//),
+ P.regexp(/(?:https?|gemini|gopher|matrix|ipns|ipfs|finger|cool|web\+\w+):\/\//),
P.seq([P.notMatch(P.alt([close, space])), P.char], 1).many(1),
close,
]).text();