fix: RSS系ウィジェットにフィードURL検査機構を追加 (#17831)

This commit is contained in:
おさむのひと
2026-07-31 16:11:39 +09:00
committed by GitHub
parent f74a303556
commit 8dd59a8c06
5 changed files with 42 additions and 10 deletions
+8
View File
@@ -27,6 +27,14 @@ export function extractDomain(url: string) {
return match ? match[1] : null;
}
export function tryParseUrl(url: string | URL, base?: string | URL): URL | null {
try {
return new URL(url, base);
} catch {
return null;
}
}
export function maybeMakeRelative(urlStr: string, baseStr: string): string {
try {
const baseObj = new URL(baseStr);
@@ -31,6 +31,7 @@ import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js';
import { url as baseUrl } from '@@/js/config.js';
import { tryParseUrl } from '@@/js/url.js';
import MkMarqueeText from '@/components/MkMarqueeText.vue';
import { shuffle } from '@/utility/shuffle.js';
@@ -56,8 +57,8 @@ const tick = () => {
}
items.value = feed.items.filter((item) => {
if (!item.link) return false;
const itemUrl = new URL(item.link, baseUrl);
return ['http:', 'https:'].includes(itemUrl.protocol);
const itemUrl = tryParseUrl(item.link, baseUrl);
return itemUrl != null && ['http:', 'https:'].includes(itemUrl.protocol);
});
fetching.value = false;
key.value++;
+4 -3
View File
@@ -24,10 +24,11 @@ import { ref, watch, computed } from 'vue';
import * as Misskey from 'misskey-js';
import { url as base } from '@@/js/config.js';
import { useInterval } from '@@/js/use-interval.js';
import { tryParseUrl } from '@@/js/url.js';
import { useWidgetPropsManager } from './widget.js';
import { i18n } from '@/i18n.js';
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
import { i18n } from '@/i18n.js';
import MkContainer from '@/components/MkContainer.vue';
const name = 'rss';
@@ -83,8 +84,8 @@ const tick = () => {
.then((feed: Misskey.entities.FetchRssResponse) => {
rawItems.value = feed.items.filter((item) => {
if (!item.link) return false;
const itemUrl = new URL(item.link, base);
return ['http:', 'https:'].includes(itemUrl.protocol);
const itemUrl = tryParseUrl(item.link, base);
return itemUrl != null && ['http:', 'https:'].includes(itemUrl.protocol);
});
fetching.value = false;
});
@@ -29,15 +29,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, watch, computed } from 'vue';
import * as Misskey from 'misskey-js';
import { url as base } from '@@/js/config.js';
import { useInterval } from '@@/js/use-interval.js';
import { tryParseUrl } from '@@/js/url.js';
import { useWidgetPropsManager } from './widget.js';
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
import MkMarqueeText from '@/components/MkMarqueeText.vue';
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
import MkMarqueeText from '@/components/MkMarqueeText.vue';
import MkContainer from '@/components/MkContainer.vue';
import { shuffle } from '@/utility/shuffle.js';
import { i18n } from '@/i18n.js';
import { url as base } from '@@/js/config.js';
import { useInterval } from '@@/js/use-interval.js';
const name = 'rssTicker';
@@ -123,8 +124,8 @@ const tick = () => {
.then((feed: Misskey.entities.FetchRssResponse) => {
rawItems.value = feed.items.filter((item) => {
if (!item.link) return false;
const itemUrl = new URL(item.link, base);
return ['http:', 'https:'].includes(itemUrl.protocol);
const itemUrl = tryParseUrl(item.link, base);
return itemUrl != null && ['http:', 'https:'].includes(itemUrl.protocol);
});
fetching.value = false;
key.value++;
+21
View File
@@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, expect, test } from 'vitest';
import { tryParseUrl } from '@@/js/url.js';
describe('tryParseUrl', () => {
test('parses an absolute URL', () => {
expect(tryParseUrl('https://example.com/path')?.href).toBe('https://example.com/path');
});
test('parses a relative URL against a base URL', () => {
expect(tryParseUrl('/path', 'https://example.com/base')?.href).toBe('https://example.com/path');
});
test('returns null for an invalid URL', () => {
expect(tryParseUrl('https://[invalid')).toBeNull();
});
});