diff --git a/lib/registry-dev.test.ts b/lib/registry-dev.test.ts index bcca32857d..27c453b4c8 100644 --- a/lib/registry-dev.test.ts +++ b/lib/registry-dev.test.ts @@ -41,6 +41,15 @@ const fakeDirectories = { handler: (ctx: Context) => ({ title: String(ctx.get('fromOuter')), link: 'https://example.com', item: [], allowEmpty: true }), }, }, + '/redirect.ts': { + route: { + path: '/redirect', + name: 'Redirect', + handler: (ctx: Context) => { + ctx.set('redirect', '/flat/single'); + }, + }, + }, '/boom.ts': { route: { path: '/boom', @@ -90,7 +99,7 @@ afterAll(() => { const buildApp = () => { const namespaces: NamespacesType = {}; const dev = createDevRegistry({ routesDirectory, namespaces }); - const app = new Hono<{ Variables: { fromOuter: string; data: Data; apiData: { ok: boolean } } }>(); + const app = new Hono<{ Variables: { fromOuter: string; data: Data; apiData: { ok: boolean }; redirect: string } }>(); app.use(async (ctx, next) => { ctx.set('fromOuter', 'bridged'); await next(); @@ -98,6 +107,10 @@ const buildApp = () => { if (apiData) { return ctx.json(apiData); } + const redirect = ctx.get('redirect'); + if (redirect) { + return ctx.redirect(redirect, 301); + } const data = ctx.get('data'); if (data) { return ctx.json(data); @@ -143,6 +156,13 @@ describe('createDevRegistry', () => { expect(body.title).toBe('bridged'); }); + it('finalizes handlers that only set a redirect', async () => { + const { app } = buildApp(); + const response = await app.request('/flat/redirect'); + expect(response.status).toBe(301); + expect(response.headers.get('location')).toBe('/flat/single'); + }); + it('serves nested namespaces', async () => { const { app } = buildApp(); const response = await app.request('/github/enterprise/news'); diff --git a/lib/registry-dev.ts b/lib/registry-dev.ts index 904cb717d7..8e396ed681 100644 --- a/lib/registry-dev.ts +++ b/lib/registry-dev.ts @@ -46,7 +46,7 @@ export function createDevRegistry({ routesDirectory, namespaces }: { routesDirec outer.set(key as never, value as never); } } - if (!ctx.finalized && (ctx.get('data') || ctx.get('apiData'))) { + if (!ctx.finalized && (ctx.get('data') || ctx.get('apiData') || ctx.get('redirect') || ctx.get('no-content'))) { // Data-producing handlers return undefined (the outer template middleware renders the // bridged vars); finalize so Hono does not raise "Context is not finalized". ctx.res = new Response(null, { status: 204 }); @@ -104,7 +104,7 @@ export function createDevRegistry({ routesDirectory, namespaces }: { routesDirec outerContexts.set(ctx.req.raw, ctx); try { const response = await subApp.fetch(ctx.req.raw); - if (ctx.get('data') || ctx.get('apiData')) { + if (ctx.get('data') || ctx.get('apiData') || ctx.get('redirect') || ctx.get('no-content')) { // The upstream template middleware renders from the bridged vars return; } diff --git a/lib/routes/10000link/info.ts b/lib/routes/10000link/info.ts index 548f422f4c..47235bf88d 100644 --- a/lib/routes/10000link/info.ts +++ b/lib/routes/10000link/info.ts @@ -3,16 +3,22 @@ import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate, parseRelativeDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'newslists', id } = ctx.req.param(); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'https://info.10000link.com'; diff --git a/lib/routes/199it/index.tsx b/lib/routes/199it/index.tsx index bb6c7f41a7..5fd1470f06 100644 --- a/lib/routes/199it/index.tsx +++ b/lib/routes/199it/index.tsx @@ -15,7 +15,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'https://www.199it.com'; - const targetUrl: string = new URL(category, baseUrl).href; + const targetUrl = `${baseUrl}/${category}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/1lou/index.ts b/lib/routes/1lou/index.ts index ede610a7fd..23ce9c4371 100644 --- a/lib/routes/1lou/index.ts +++ b/lib/routes/1lou/index.ts @@ -18,7 +18,7 @@ export const handler = async (ctx: Context) => { .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) .join('&'); - const currentUrl = new URL(`${params && params.endsWith('.htm') ? params : `${params}.htm`}${queryString ? `?${queryString}` : ''}`, rootUrl).href; + const currentUrl = `${rootUrl}/${params && params.endsWith('.htm') ? params : `${params}.htm`}${queryString ? `?${queryString}` : ''}`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/423down/index.ts b/lib/routes/423down/index.ts index d148b319cf..d9c3fd20e9 100644 --- a/lib/routes/423down/index.ts +++ b/lib/routes/423down/index.ts @@ -13,7 +13,7 @@ export const handler = async (ctx) => { const domain = '423down.com'; const rootUrl = `https://www.${domain}`; - const currentUrl = new URL(category, rootUrl).href; + const currentUrl = `${rootUrl}/${category}`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/4ksj/forum.tsx b/lib/routes/4ksj/forum.tsx index ff6f115002..f328d2b404 100644 --- a/lib/routes/4ksj/forum.tsx +++ b/lib/routes/4ksj/forum.tsx @@ -2,12 +2,14 @@ import { load } from 'cheerio'; import { raw } from 'hono/html'; import { renderToString } from 'hono/jsx/dom/server'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Language, Route } from '@/types'; import cache from '@/utils/cache'; import md5 from '@/utils/md5'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import { isValidHost } from '@/utils/valid-host'; const renderDescription = ({ images, title, keys, details, description, info, links }) => renderToString( @@ -79,6 +81,10 @@ function stringtoHex(acSTR) { async function handler(ctx) { const { id = '4k-uhd-1' } = ctx.req.param(); + if (!isValidHost(id)) { + throw new InvalidParameterError('Invalid id'); + } + const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 25; const rootUrl = 'https://www.4ksj.com'; diff --git a/lib/routes/6v123/index.ts b/lib/routes/6v123/index.ts index 447b423fd1..a2c58cafee 100644 --- a/lib/routes/6v123/index.ts +++ b/lib/routes/6v123/index.ts @@ -17,7 +17,7 @@ export const handler = async (ctx: Context): Promise => { const encoding = 'gb2312'; const baseUrl = 'https://www.hao6v.me'; - const targetUrl: string = new URL(category.startsWith('gvod') ? `${category}.html` : category, baseUrl).href; + const targetUrl = `${baseUrl}/${category.startsWith('gvod') ? `${category}.html` : category}`; const response = await ofetch(targetUrl, { responseType: 'arrayBuffer', diff --git a/lib/routes/abc/index.ts b/lib/routes/abc/index.ts index 03ce451a64..aa3bf2261f 100644 --- a/lib/routes/abc/index.ts +++ b/lib/routes/abc/index.ts @@ -42,8 +42,8 @@ async function handler(ctx) { let currentUrl: string; let documentId; - if (Number.isNaN(category)) { - currentUrl = new URL(category, rootUrl).href; + if (Number.isNaN(Number(category))) { + currentUrl = `${rootUrl}/${category}`; } else { documentId = category; const feedUrl = new URL(`news/feed/${documentId}/rss.xml`, rootUrl).href; diff --git a/lib/routes/accessbriefing/index.ts b/lib/routes/accessbriefing/index.ts index 3a60978a05..d21ce9051a 100644 --- a/lib/routes/accessbriefing/index.ts +++ b/lib/routes/accessbriefing/index.ts @@ -12,7 +12,7 @@ export const handler = async (ctx) => { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'https://www.accessbriefing.com'; - const currentUrl = new URL(category, rootUrl).href; + const currentUrl = `${rootUrl}/${category}`; const apiUrl = new URL('Ajax/GetPagedArticles', rootUrl).href; const { data: currentResponse } = await got(currentUrl); diff --git a/lib/routes/amazfitwatchfaces/index.ts b/lib/routes/amazfitwatchfaces/index.ts index 51d5d22108..3c91d975b3 100644 --- a/lib/routes/amazfitwatchfaces/index.ts +++ b/lib/routes/amazfitwatchfaces/index.ts @@ -3,16 +3,22 @@ import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { device, sort, searchParams } = ctx.req.param(); + if (!isValidHost(device)) { + throw new InvalidParameterError('Invalid device'); + } + const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'https://amazfitwatchfaces.com'; diff --git a/lib/routes/app-sales/index.ts b/lib/routes/app-sales/index.ts index 87dea3883b..d0118b5277 100644 --- a/lib/routes/app-sales/index.ts +++ b/lib/routes/app-sales/index.ts @@ -2,14 +2,20 @@ import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; +import { isValidHost } from '@/utils/valid-host'; import { baseUrl, fetchItems } from './util'; export const handler = async (ctx: Context): Promise => { const { category = 'highlights', country = 'us' } = ctx.req.param(); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = Number(ctx.req.query('limit') ?? '100'); const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, baseUrl).href; diff --git a/lib/routes/apple/exchange-repair.ts b/lib/routes/apple/exchange-repair.ts index 64d595a11b..26ad1d408d 100644 --- a/lib/routes/apple/exchange-repair.ts +++ b/lib/routes/apple/exchange-repair.ts @@ -1,9 +1,11 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; const host = 'https://support.apple.com/'; @@ -33,6 +35,10 @@ export const route: Route = { async function handler(ctx) { const country = ctx.req.param('country') ?? ''; + if (country && !isValidHost(country)) { + throw new InvalidParameterError('Invalid country'); + } + const link = new URL(`${country}/service-programs`, host).href; const response = await got(link); diff --git a/lib/routes/apple/security-releases.ts b/lib/routes/apple/security-releases.ts index ba76da9f09..0c73d1face 100644 --- a/lib/routes/apple/security-releases.ts +++ b/lib/routes/apple/security-releases.ts @@ -3,16 +3,22 @@ import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/security-releases'; export const handler = async (ctx: Context): Promise => { const { language: languageParam = 'en-us' } = ctx.req.param(); + if (!isValidHost(languageParam)) { + throw new InvalidParameterError('Invalid language'); + } + const language = languageParam as Language; const limit = Number(ctx.req.query('limit') ?? '30'); diff --git a/lib/routes/aqara/post.tsx b/lib/routes/aqara/post.tsx index b69c7ebc0f..d58d712779 100644 --- a/lib/routes/aqara/post.tsx +++ b/lib/routes/aqara/post.tsx @@ -38,7 +38,7 @@ async function handler(ctx) { const filterRegion = filterMatches[1]; const filterType = filterMatches[2] === 'tag' ? 'tags' : filterMatches[2] === 'category' ? 'categories' : filterMatches[2]; const filterKeyword = decodeURI(filterMatches[3].split('/').pop()); - const filterApiUrl = new URL(`${filterRegion}/${apiSlug}/${filterType}?search=${filterKeyword}`, rootUrl).href; + const filterApiUrl = `${rootUrl}/${filterRegion}/${apiSlug}/${filterType}?search=${filterKeyword}`; const { data: filterResponse } = await got(filterApiUrl); @@ -47,7 +47,7 @@ async function handler(ctx) { if (filter?.id ?? undefined) { filterName = filter.name ?? filterKeyword; currentUrl = filter.link ?? currentUrl; - apiUrl = new URL(`${filterRegion}/${apiSlug}/posts?_embed=true&per_page=${limit}&${filterType}=${filter.id}`, rootUrl).href; + apiUrl = `${rootUrl}/${filterRegion}/${apiSlug}/posts?_embed=true&per_page=${limit}&${filterType}=${filter.id}`; } } diff --git a/lib/routes/auto-stats/index.ts b/lib/routes/auto-stats/index.ts index d66ba7fcb8..2d81b5b221 100644 --- a/lib/routes/auto-stats/index.ts +++ b/lib/routes/auto-stats/index.ts @@ -1,11 +1,13 @@ import { load } from 'cheerio'; import iconv from 'iconv-lite'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import { isValidHost } from '@/utils/valid-host'; export const route: Route = { path: '/:category?', @@ -30,6 +32,10 @@ export const route: Route = { async function handler(ctx) { const { category = 'xxkd' } = ctx.req.param(); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'http://www.auto-stats.org.cn'; diff --git a/lib/routes/bnu/mba.ts b/lib/routes/bnu/mba.ts index d627e5dfb3..6be142fbe1 100644 --- a/lib/routes/bnu/mba.ts +++ b/lib/routes/bnu/mba.ts @@ -10,7 +10,7 @@ export const handler = async (ctx) => { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const rootUrl = 'https://mba.bnu.edu.cn'; - const currentUrl = new URL(`${category.replace(/\/$/, '')}/`, rootUrl).href; + const currentUrl = `${rootUrl}/${category.replace(/\/$/, '')}/`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/cbaigui/index.ts b/lib/routes/cbaigui/index.ts index 39bf0757bd..d1601beaef 100644 --- a/lib/routes/cbaigui/index.ts +++ b/lib/routes/cbaigui/index.ts @@ -28,7 +28,7 @@ async function handler(ctx) { let filterName; const path = ctx.req.param('path') ?? ''; - const currentUrl = new URL(`/${path}`, rootUrl).href; + const currentUrl = `${rootUrl}/${path}`; let apiUrl = new URL(`${apiSlug}/posts?_embed=true&per_page=${limit}`, rootUrl).href; const filterMatches = path.match(/^post-(tag|category)\/(.*)$/); diff --git a/lib/routes/ccagm/index.ts b/lib/routes/ccagm/index.ts index 097d0c6288..e6c95f07c1 100644 --- a/lib/routes/ccagm/index.ts +++ b/lib/routes/ccagm/index.ts @@ -15,7 +15,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '10'); const baseUrl = 'http://www.ccagm.org.cn'; - const targetUrl: string = new URL(category, baseUrl).href; + const targetUrl = `${baseUrl}/${category}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/cccmc/index.ts b/lib/routes/cccmc/index.ts index 5500c30f9b..b98794e57a 100644 --- a/lib/routes/cccmc/index.ts +++ b/lib/routes/cccmc/index.ts @@ -14,7 +14,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '15'); const baseUrl = 'https://www.cccmc.org.cn'; - const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, baseUrl).href; + const targetUrl = `${baseUrl}/${category.endsWith('/') ? category : `${category}/`}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/ccg/index.ts b/lib/routes/ccg/index.ts index ff908ca92e..cea64b0188 100644 --- a/lib/routes/ccg/index.ts +++ b/lib/routes/ccg/index.ts @@ -3,16 +3,22 @@ import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'news' } = ctx.req.param(); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = Number(ctx.req.query('limit') ?? '7'); const baseUrl = 'http://www.ccg.org.cn'; diff --git a/lib/routes/chinacdc/index.ts b/lib/routes/chinacdc/index.ts index 2236c24cb6..776faa279b 100644 --- a/lib/routes/chinacdc/index.ts +++ b/lib/routes/chinacdc/index.ts @@ -16,7 +16,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '11'); const rootUrl = 'https://www.chinacdc.cn'; - const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, rootUrl).href; + const targetUrl = `${rootUrl}/${category.endsWith('/') ? category : `${category}/`}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/chinadaily/language.ts b/lib/routes/chinadaily/language.ts index b8d97fb28c..5d0df3668f 100644 --- a/lib/routes/chinadaily/language.ts +++ b/lib/routes/chinadaily/language.ts @@ -17,7 +17,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'https://language.chinadaily.com.cn'; - const targetUrl: string = new URL(category, baseUrl).href; + const targetUrl = `${baseUrl}/${category}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/cngold/index.ts b/lib/routes/cngold/index.ts index ebe63c4aca..cf36064598 100644 --- a/lib/routes/cngold/index.ts +++ b/lib/routes/cngold/index.ts @@ -1,12 +1,18 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Language, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; export const handler = async (ctx) => { const { category = 'news-325' } = ctx.req.param(); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 12; const rootUrl = 'https://www.cngold.org.cn'; diff --git a/lib/routes/cnljxh/index.ts b/lib/routes/cnljxh/index.ts index 72484cd162..c29a7079d6 100644 --- a/lib/routes/cnljxh/index.ts +++ b/lib/routes/cnljxh/index.ts @@ -3,14 +3,20 @@ import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; export const handler = async (ctx: Context): Promise => { const { category = 'news', id = '10' } = ctx.req.param(); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = Number(ctx.req.query('limit') ?? '20'); const baseUrl = 'https://www.cnljxh.org.cn'; diff --git a/lib/routes/cqu/index.ts b/lib/routes/cqu/index.ts index 76fa362707..112ff6e24b 100644 --- a/lib/routes/cqu/index.ts +++ b/lib/routes/cqu/index.ts @@ -37,7 +37,7 @@ export const route: Route = { async function handler(ctx) { const { path = 'index/tzgg' } = ctx.req.param(); const baseUrl = 'http://jwc.cqu.edu.cn'; - const url = new URL(`${path}.htm`, baseUrl).href; + const url = `${baseUrl}/${path}.htm`; const response = await ofetch(url); const $ = load(response); diff --git a/lib/routes/cursor/blog.ts b/lib/routes/cursor/blog.ts index 6db1ea0376..7b3b3cecf8 100644 --- a/lib/routes/cursor/blog.ts +++ b/lib/routes/cursor/blog.ts @@ -1,13 +1,19 @@ import { load } from 'cheerio'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; export const handler = async (ctx: Context): Promise => { const { topic, locale } = ctx.req.param(); + if (locale && !isValidHost(locale)) { + throw new InvalidParameterError('Invalid locale'); + } + const limit = Number(ctx.req.query('limit') ?? '10'); const baseUrl = 'https://cursor.com'; diff --git a/lib/routes/cursor/changelog.ts b/lib/routes/cursor/changelog.ts index 496069982d..cefb0c10af 100644 --- a/lib/routes/cursor/changelog.ts +++ b/lib/routes/cursor/changelog.ts @@ -3,13 +3,19 @@ import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; export const handler = async (ctx: Context): Promise => { const locale = ctx.req.param('locale'); + if (locale && !isValidHost(locale)) { + throw new InvalidParameterError('Invalid locale'); + } + const limit = Number(ctx.req.query('limit') ?? '100'); const baseUrl = 'https://cursor.com'; diff --git a/lib/routes/deepl/blog.ts b/lib/routes/deepl/blog.ts index 9f03551797..a885c52ea7 100644 --- a/lib/routes/deepl/blog.ts +++ b/lib/routes/deepl/blog.ts @@ -3,16 +3,22 @@ import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { lang = 'en' } = ctx.req.param(); + if (!isValidHost(lang)) { + throw new InvalidParameterError('Invalid lang'); + } + const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'https://www.deepl.com'; diff --git a/lib/routes/dehenglaw/index.ts b/lib/routes/dehenglaw/index.ts index 7c6e3e057e..884f7f1bc0 100644 --- a/lib/routes/dehenglaw/index.ts +++ b/lib/routes/dehenglaw/index.ts @@ -1,14 +1,20 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { language = 'CN', category = 'paper' } = ctx.req.param(); + if (!isValidHost(language)) { + throw new InvalidParameterError('Invalid language'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 6; const rootUrl = 'https://www.dehenglaw.com'; diff --git a/lib/routes/dekudeals/index.ts b/lib/routes/dekudeals/index.ts index add54ce9ae..c7d2687a06 100644 --- a/lib/routes/dekudeals/index.ts +++ b/lib/routes/dekudeals/index.ts @@ -1,8 +1,10 @@ import { load } from 'cheerio'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; +import { isValidHost } from '@/utils/valid-host'; export const route: Route = { path: '/:type', @@ -16,6 +18,9 @@ export const route: Route = { async function handler(ctx: Context) { const { type } = ctx.req.param(); + if (!isValidHost(type)) { + throw new InvalidParameterError('Invalid type'); + } const host = 'https://www.dekudeals.com/'; const link = new URL(type, host).href; diff --git a/lib/routes/dn/news.ts b/lib/routes/dn/news.ts index 666cdebb98..72c9b43810 100644 --- a/lib/routes/dn/news.ts +++ b/lib/routes/dn/news.ts @@ -1,10 +1,12 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Language, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; @@ -42,6 +44,10 @@ export const route: Route = { async function handler(ctx) { const { language, category = '' } = ctx.req.param(); + if (!isValidHost(language)) { + throw new InvalidParameterError('Invalid language'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10; const rootUrl = 'https://dn.com'; diff --git a/lib/routes/douban/other/explore-column.ts b/lib/routes/douban/other/explore-column.ts index 15ecfa6861..fc01746dda 100644 --- a/lib/routes/douban/other/explore-column.ts +++ b/lib/routes/douban/other/explore-column.ts @@ -1,7 +1,9 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import got from '@/utils/got'; +import { isValidHost } from '@/utils/valid-host'; const host = 'https://www.douban.com/explore/column/'; export const route: Route = { @@ -16,6 +18,10 @@ export const route: Route = { async function handler(ctx) { const id = ctx.req.param('id'); + if (!isValidHost(id)) { + throw new InvalidParameterError('Invalid id'); + } + const link = new URL(id, host).href; const response = await got.get(link); const $ = load(response.data); diff --git a/lib/routes/freecomputerbooks/index.tsx b/lib/routes/freecomputerbooks/index.tsx index a062e0894b..4c63f13793 100644 --- a/lib/routes/freecomputerbooks/index.tsx +++ b/lib/routes/freecomputerbooks/index.tsx @@ -2,9 +2,11 @@ import { load } from 'cheerio'; import { raw } from 'hono/html'; import { renderToString } from 'hono/jsx/dom/server'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; +import { isValidHost } from '@/utils/valid-host'; const baseURL = 'https://freecomputerbooks.com/'; @@ -43,6 +45,10 @@ export const route: Route = { async function handler(ctx) { const categoryId = ctx.req.param('category')?.trim(); + if (categoryId && !isValidHost(categoryId)) { + throw new InvalidParameterError('Invalid category'); + } + const requestURL = categoryId ? new URL(`${categoryId}.html`, baseURL).href : baseURL; const $ = await cheerioLoad(requestURL); diff --git a/lib/routes/gitstar-ranking/index.tsx b/lib/routes/gitstar-ranking/index.tsx index 2d7bad8926..ae43b09768 100644 --- a/lib/routes/gitstar-ranking/index.tsx +++ b/lib/routes/gitstar-ranking/index.tsx @@ -4,9 +4,11 @@ import type { Element } from 'domhandler'; import type { Context } from 'hono'; import { renderToString } from 'hono/jsx/dom/server'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; +import { isValidHost } from '@/utils/valid-host'; const renderDescription = ({ images, stargazersCount, language, description }) => renderToString( @@ -49,6 +51,10 @@ const renderDescription = ({ images, stargazersCount, language, description }) = export const handler = async (ctx: Context): Promise => { const { category = 'repositories' } = ctx.req.param(); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = Number(ctx.req.query('limit') ?? '100'); const baseUrl = 'https://gitstar-ranking.com'; diff --git a/lib/routes/go/mhlw/pdf.ts b/lib/routes/go/mhlw/pdf.ts index e7ac8da2b2..039c180a09 100644 --- a/lib/routes/go/mhlw/pdf.ts +++ b/lib/routes/go/mhlw/pdf.ts @@ -8,7 +8,7 @@ export const handler = async (ctx) => { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'https://www.mhlw.go.jp'; - const currentUrl = new URL(category.endsWith('.html') ? category : `${category}.html`, rootUrl).href; + const currentUrl = `${rootUrl}/${category.endsWith('.html') ? category : `${category}.html`}`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/gov/ah/kjt.ts b/lib/routes/gov/ah/kjt.ts index cfa012e4ab..2cd2ab9e6a 100644 --- a/lib/routes/gov/ah/kjt.ts +++ b/lib/routes/gov/ah/kjt.ts @@ -11,7 +11,7 @@ export const handler = async (ctx) => { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const rootUrl = 'https://kjt.ah.gov.cn'; - const currentUrl = new URL(`${category.replace(/\/$/, '').replace(/\/index\.html$/, '')}/`, rootUrl).href; + const currentUrl = `${rootUrl}/${category.replace(/\/$/, '').replace(/\/index\.html$/, '')}/`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/gov/chongqing/gzw.ts b/lib/routes/gov/chongqing/gzw.ts index 8111bc0238..03ceb828fc 100644 --- a/lib/routes/gov/chongqing/gzw.ts +++ b/lib/routes/gov/chongqing/gzw.ts @@ -33,7 +33,7 @@ async function handler(ctx) { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 15; const rootUrl = 'https://gzw.cq.gov.cn'; - const currentUrl = new URL(category, rootUrl).href; + const currentUrl = `${rootUrl}/${category}`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/gov/forestry/gjlckjdjt.ts b/lib/routes/gov/forestry/gjlckjdjt.ts index 8fc23b6ebf..3a6ca04daf 100644 --- a/lib/routes/gov/forestry/gjlckjdjt.ts +++ b/lib/routes/gov/forestry/gjlckjdjt.ts @@ -1,9 +1,11 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Language, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; @@ -35,6 +37,10 @@ export const route: Route = { async function handler(ctx) { const { category = 'gjlckjdjt' } = ctx.req.param(); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'http://www.forestry.gov.cn'; diff --git a/lib/routes/gov/general/general.ts b/lib/routes/gov/general/general.ts index 236306b791..ff82fd4f96 100644 --- a/lib/routes/gov/general/general.ts +++ b/lib/routes/gov/general/general.ts @@ -1,5 +1,6 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; // 来人拯救一下啊( >﹏<。) @@ -40,6 +41,7 @@ import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import { isValidHost } from '@/utils/valid-host'; import { finishArticleItem } from '@/utils/wechat-mp'; import { renderZcjdpt } from './templates/zcjdpt'; @@ -49,6 +51,9 @@ const gdgov = async (info, ctx) => { .split('/') .filter((item) => item !== ''); const [site, branch] = path; + if (!isValidHost(site) || !isValidHost(branch)) { + throw new InvalidParameterError('Invalid site or branch'); + } // 网站 const pathstartat = info.pathstartat === undefined ? 0 : info.pathstartat; diff --git a/lib/routes/gov/lswz/index.ts b/lib/routes/gov/lswz/index.ts index f9a208aa0d..78412391dd 100644 --- a/lib/routes/gov/lswz/index.ts +++ b/lib/routes/gov/lswz/index.ts @@ -11,7 +11,7 @@ export const handler = async (ctx) => { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 25; const rootUrl = 'https://www.lswz.gov.cn'; - const currentUrl = new URL(`${category}.shtml`, rootUrl).href; + const currentUrl = `${rootUrl}/${category}.shtml`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/gov/moa/gjs.ts b/lib/routes/gov/moa/gjs.ts index 8ccf7f8029..d3cc608ccd 100644 --- a/lib/routes/gov/moa/gjs.ts +++ b/lib/routes/gov/moa/gjs.ts @@ -15,7 +15,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'http://www.gjs.moa.gov.cn'; - const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, baseUrl).href; + const targetUrl = `${baseUrl}/${category.endsWith('/') ? category : `${category}/`}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/gov/nsfc/index.ts b/lib/routes/gov/nsfc/index.ts index f899b5bb66..482585c40e 100644 --- a/lib/routes/gov/nsfc/index.ts +++ b/lib/routes/gov/nsfc/index.ts @@ -67,7 +67,7 @@ async function handler(ctx) { } const rootUrl = 'https://www.nsfc.gov.cn'; - const currentUrl = new URL((thePath.endsWith('/more') ? `${thePath}.htm` : thePath) || 'publish/portal0/tab442/', rootUrl).href; + const currentUrl = `${rootUrl}${(thePath.endsWith('/more') ? `${thePath}.htm` : thePath) || '/publish/portal0/tab442/'}`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/gov/safe/util.tsx b/lib/routes/gov/safe/util.tsx index 65838a7743..04b8c87275 100644 --- a/lib/routes/gov/safe/util.tsx +++ b/lib/routes/gov/safe/util.tsx @@ -1,9 +1,11 @@ import { load } from 'cheerio'; import { renderToString } from 'hono/jsx/dom/server'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Language } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; const rootUrl = 'https://www.safe.gov.cn'; @@ -16,6 +18,10 @@ const zxfkCategoryApis = { }; const processZxfkItems = async (site = 'beijing', category = 'ywzx', limit = 3) => { + if (!isValidHost(site)) { + throw new InvalidParameterError('Invalid site'); + } + const apiUrl = new URL(`${zxfkCategoryApis[category]}${site}`, rootUrl).href; const currentUrl = new URL(`${site}/${category}/index.html`, rootUrl).href; diff --git a/lib/routes/gov/sh/fgw/index.tsx b/lib/routes/gov/sh/fgw/index.tsx index 1ab206ca23..f655be74cb 100644 --- a/lib/routes/gov/sh/fgw/index.tsx +++ b/lib/routes/gov/sh/fgw/index.tsx @@ -28,7 +28,7 @@ export const handler = async (ctx) => { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const rootUrl = 'https://fgw.sh.gov.cn'; - const currentUrl = new URL(`${category}/index.html`, rootUrl).href; + const currentUrl = `${rootUrl}/${category}/index.html`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/gov/shenzhen/zzb/index.ts b/lib/routes/gov/shenzhen/zzb/index.ts index b3c6a97cc6..924196ec31 100644 --- a/lib/routes/gov/shenzhen/zzb/index.ts +++ b/lib/routes/gov/shenzhen/zzb/index.ts @@ -1,9 +1,11 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import { isValidHost } from '@/utils/valid-host'; const rootURL = 'http://www.zzb.sz.gov.cn/'; @@ -36,6 +38,10 @@ export const route: Route = { async function handler(ctx) { const categoryID = ctx.req.param('caty'); + if (!isValidHost(categoryID)) { + throw new InvalidParameterError('Invalid caty'); + } + const page = ctx.req.param('page') ?? '1'; const pageParam = Number.parseInt(page) > 1 ? `_${page}` : ''; diff --git a/lib/routes/gov/suzhou/fg.ts b/lib/routes/gov/suzhou/fg.ts index 2f5e8f05ae..1602e481e1 100644 --- a/lib/routes/gov/suzhou/fg.ts +++ b/lib/routes/gov/suzhou/fg.ts @@ -30,7 +30,7 @@ async function handler(ctx) { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'https://fg.suzhou.gov.cn'; - const currentUrl = new URL(`${categoryPath}.shtml`, rootUrl).href; + const currentUrl = `${rootUrl}/${categoryPath}.shtml`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/grainoil/category.ts b/lib/routes/grainoil/category.ts index abd7f25295..600aa1b7fc 100644 --- a/lib/routes/grainoil/category.ts +++ b/lib/routes/grainoil/category.ts @@ -3,15 +3,21 @@ import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import { isValidHost } from '@/utils/valid-host'; export const handler = async (ctx: Context): Promise => { const { category, id } = ctx.req.param(); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'http://load.grainoil.com.cn'; diff --git a/lib/routes/hit/hitgs.ts b/lib/routes/hit/hitgs.ts index 31f5a43207..75acb731e4 100644 --- a/lib/routes/hit/hitgs.ts +++ b/lib/routes/hit/hitgs.ts @@ -3,16 +3,22 @@ import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Language, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { id = 'tzgg' } = ctx.req.param(); + if (!isValidHost(id)) { + throw new InvalidParameterError('Invalid id'); + } + const limit = Number(ctx.req.query('limit') ?? '10'); const baseUrl = 'https://hitgs.hit.edu.cn'; diff --git a/lib/routes/hubu/index.ts b/lib/routes/hubu/index.ts index 2830d4a9a3..1655b125aa 100644 --- a/lib/routes/hubu/index.ts +++ b/lib/routes/hubu/index.ts @@ -10,7 +10,7 @@ export const handler = async (ctx) => { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const rootUrl = 'https://www.hubu.edu.cn'; - const currentUrl = new URL(`${category}.htm`, rootUrl).href; + const currentUrl = `${rootUrl}/${category}.htm`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/hubu/zhxy.ts b/lib/routes/hubu/zhxy.ts index 2dd9fd5728..d288a96526 100644 --- a/lib/routes/hubu/zhxy.ts +++ b/lib/routes/hubu/zhxy.ts @@ -10,7 +10,7 @@ export const handler = async (ctx) => { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const rootUrl = 'https://zhxy.hubu.edu.cn'; - const currentUrl = new URL(`${category}.htm`, rootUrl).href; + const currentUrl = `${rootUrl}/${category}.htm`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/hust/gs.ts b/lib/routes/hust/gs.ts index 9cc11366da..9e8b6d2962 100644 --- a/lib/routes/hust/gs.ts +++ b/lib/routes/hust/gs.ts @@ -10,7 +10,7 @@ export const handler = async (ctx) => { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 16; const rootUrl = 'https://gs.hust.edu.cn'; - const currentUrl = new URL(`${category}.htm`, rootUrl).href; + const currentUrl = `${rootUrl}/${category}.htm`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/hust/mse.ts b/lib/routes/hust/mse.ts index 6b4981c0b4..73829c8162 100644 --- a/lib/routes/hust/mse.ts +++ b/lib/routes/hust/mse.ts @@ -11,7 +11,7 @@ export const handler = async (ctx) => { const domain = 'mse.hust.edu.cn'; const rootUrl = `https://${domain}`; - const currentUrl = new URL(`${category}.htm`, rootUrl).href; + const currentUrl = `${rootUrl}/${category}.htm`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/ianspriggs/index.ts b/lib/routes/ianspriggs/index.ts index 97f33171ed..3e6a874a4b 100644 --- a/lib/routes/ianspriggs/index.ts +++ b/lib/routes/ianspriggs/index.ts @@ -1,9 +1,11 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Language, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; @@ -30,6 +32,10 @@ export const route: Route = { async function handler(ctx) { const { category = 'portraits' } = ctx.req.param(); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const author = 'Ian Spriggs'; diff --git a/lib/routes/infoq/presentations.ts b/lib/routes/infoq/presentations.ts index e7b2544822..a9d7104091 100644 --- a/lib/routes/infoq/presentations.ts +++ b/lib/routes/infoq/presentations.ts @@ -1,14 +1,20 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Language, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { conference } = ctx.req.param(); + if (conference && !isValidHost(conference)) { + throw new InvalidParameterError('Invalid conference'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 12; const rootUrl = 'https://www.infoq.com'; diff --git a/lib/routes/investor/index.ts b/lib/routes/investor/index.ts index cadf31bf0e..d893660c29 100644 --- a/lib/routes/investor/index.ts +++ b/lib/routes/investor/index.ts @@ -14,7 +14,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '20'); const baseUrl = 'https://www.investor.org.cn'; - const targetUrl: string = new URL(id.endsWith('/') ? id : `${id}/`, baseUrl).href; + const targetUrl = `${baseUrl}/${id.endsWith('/') ? id : `${id}/`}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/iqilu/program.ts b/lib/routes/iqilu/program.ts index bd0005997e..88f57fd712 100644 --- a/lib/routes/iqilu/program.ts +++ b/lib/routes/iqilu/program.ts @@ -44,7 +44,7 @@ async function handler(ctx) { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'http://v.iqilu.com'; - const currentUrl = new URL(category, rootUrl).href; + const currentUrl = `${rootUrl}/${category}`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/jimmyspa/books.ts b/lib/routes/jimmyspa/books.ts index fd8b6903c9..9aa3f36604 100644 --- a/lib/routes/jimmyspa/books.ts +++ b/lib/routes/jimmyspa/books.ts @@ -1,10 +1,12 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; @@ -40,6 +42,10 @@ export const route: Route = { async function handler(ctx) { const language = ctx.req.param('language'); + if (!isValidHost(language)) { + throw new InvalidParameterError('Invalid language'); + } + const baseUrl = 'https://www.jimmyspa.com'; const booksListUrl = new URL(`/${language}/Books/Ajax/changeList?year=&keyword=&categoryId=0&page=1`, baseUrl).href; diff --git a/lib/routes/jimmyspa/news.ts b/lib/routes/jimmyspa/news.ts index e1d98843a8..da06d566a7 100644 --- a/lib/routes/jimmyspa/news.ts +++ b/lib/routes/jimmyspa/news.ts @@ -1,9 +1,11 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; @@ -39,6 +41,10 @@ export const route: Route = { async function handler(ctx) { const language = ctx.req.param('language'); + if (!isValidHost(language)) { + throw new InvalidParameterError('Invalid language'); + } + const rootUrl = 'https://www.jimmyspa.com'; const currentUrl = new URL(`/${language}/News/Ajax/changeList?year=&keyword=&categoryId=0&page=1`, rootUrl).href; diff --git a/lib/routes/kantarworldpanel/index.tsx b/lib/routes/kantarworldpanel/index.tsx index 20f928fdc7..b182fa1cba 100644 --- a/lib/routes/kantarworldpanel/index.tsx +++ b/lib/routes/kantarworldpanel/index.tsx @@ -92,7 +92,7 @@ async function handler(ctx) { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'https://www.kantarworldpanel.com/'; - const currentUrl = new URL(`${region}/${category}`, rootUrl).href; + const currentUrl = `${rootUrl}${region}/${category}`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/koreaherald/index.ts b/lib/routes/koreaherald/index.ts index 96b6d74078..f8b2825f43 100644 --- a/lib/routes/koreaherald/index.ts +++ b/lib/routes/koreaherald/index.ts @@ -39,7 +39,7 @@ async function handler(ctx) { const category = ctx.req.param('category') ?? 'National'; const baseUrl = 'https://www.koreaherald.com/'; - const response = await got(new URL(category, baseUrl).href); + const response = await got(`${baseUrl}${category}`); const $ = load(response.data); const title = $('ul.gnb').find('[class="on"]').length > 0 ? $('ul.gnb').find('[class="on"]').text() : $('div.nav_area > a.category').text(); const list = $('article.recent_news > ul.news_list > li') @@ -63,7 +63,7 @@ async function handler(ctx) { ); return { title: `The Korea Herald - ${title}`, - link: new URL(category, baseUrl).href, + link: `${baseUrl}${category}`, item: items, }; } diff --git a/lib/routes/lit/xwzx.ts b/lib/routes/lit/xwzx.ts index d4d4655254..e843db677a 100644 --- a/lib/routes/lit/xwzx.ts +++ b/lib/routes/lit/xwzx.ts @@ -1,11 +1,13 @@ import { load } from 'cheerio'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import { isValidHost } from '@/utils/valid-host'; export const route: Route = { path: '/xwzx/:name?', @@ -31,6 +33,10 @@ const nameProps = { async function handler(ctx: Context): Promise { const { name = 'all' } = ctx.req.param(); + if (!isValidHost(name)) { + throw new InvalidParameterError('Invalid name'); + } + const categories = name === 'all' ? Object.keys(nameProps) : [name]; const lists = await Promise.all( diff --git a/lib/routes/liulinblog/index.ts b/lib/routes/liulinblog/index.ts index 3e53dc330b..8bcda5db0f 100644 --- a/lib/routes/liulinblog/index.ts +++ b/lib/routes/liulinblog/index.ts @@ -50,7 +50,7 @@ export async function handler(ctx) { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const rootUrl = 'https://www.liulinblog.com'; - const currentUrl = subPath === '/' ? rootUrl : new URL(subPath, rootUrl).href; + const currentUrl = subPath === '/' ? rootUrl : `${rootUrl}${subPath}`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/lrepacks/index.ts b/lib/routes/lrepacks/index.ts index 044c1fa760..c23d2c5ab8 100644 --- a/lib/routes/lrepacks/index.ts +++ b/lib/routes/lrepacks/index.ts @@ -1,14 +1,20 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Language, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { category = '' } = ctx.req.param(); + if (category && !isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 12; const rootUrl = 'https://lrepacks.net'; diff --git a/lib/routes/m4/index.ts b/lib/routes/m4/index.ts index c131e5dc1e..b5854b0ed2 100644 --- a/lib/routes/m4/index.ts +++ b/lib/routes/m4/index.ts @@ -1,11 +1,13 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; @@ -36,6 +38,10 @@ export const route: Route = { export async function handler(ctx) { const [id, category = 'china'] = getSubPath(ctx).split('/').filter(Boolean); + if (!isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = `http://${id}.m4.cn`; diff --git a/lib/routes/musify/index.ts b/lib/routes/musify/index.ts index e45620508d..911a9de958 100644 --- a/lib/routes/musify/index.ts +++ b/lib/routes/musify/index.ts @@ -3,12 +3,18 @@ import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; +import { isValidHost } from '@/utils/valid-host'; export const handler = async (ctx: Context): Promise => { const { language = '' } = ctx.req.param(); + if (language && !isValidHost(language)) { + throw new InvalidParameterError('Invalid language'); + } + const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'https://musify.club'; diff --git a/lib/routes/p-articles/section.ts b/lib/routes/p-articles/section.ts index d550753a51..45d15ba9f2 100644 --- a/lib/routes/p-articles/section.ts +++ b/lib/routes/p-articles/section.ts @@ -1,8 +1,10 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Language, Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; +import { isValidHost } from '@/utils/valid-host'; import { ProcessFeed, rootUrl } from './utils'; @@ -23,6 +25,10 @@ export const route: Route = { async function handler(ctx) { let sectionName: string = ctx.req.param('section'); + if (!isValidHost(sectionName)) { + throw new InvalidParameterError('Invalid section'); + } + sectionName = sectionName.replace('-', '/'); sectionName += '/'; const sectionUrl = new URL(sectionName, rootUrl).href; diff --git a/lib/routes/papers/category.ts b/lib/routes/papers/category.ts index 3d336ef132..3b161a37d0 100644 --- a/lib/routes/papers/category.ts +++ b/lib/routes/papers/category.ts @@ -16,7 +16,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '50'); const baseUrl = 'https://papers.cool'; - const targetUrl: string = new URL(`${id}?show=${limit}`, baseUrl).href; + const targetUrl = `${baseUrl}/${id}?show=${limit}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/producereport/index.ts b/lib/routes/producereport/index.ts index 6e99f5d0df..d9e7194cc5 100644 --- a/lib/routes/producereport/index.ts +++ b/lib/routes/producereport/index.ts @@ -16,7 +16,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '10'); const baseUrl = 'https://www.producereport.com'; - const targetUrl: string = new URL(category, baseUrl).href; + const targetUrl = `${baseUrl}/${category}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/qqorw/index.ts b/lib/routes/qqorw/index.ts index 20ac53e7f2..65e15e0414 100644 --- a/lib/routes/qqorw/index.ts +++ b/lib/routes/qqorw/index.ts @@ -1,10 +1,12 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; +import { isValidHost } from '@/utils/valid-host'; export const route: Route = { path: '/:category?', @@ -34,6 +36,10 @@ export const route: Route = { async function handler(ctx) { const { category = '' } = ctx.req.param(); + if (category && !isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10; const rootUrl = 'https://qqorw.cn'; diff --git a/lib/routes/samrdprc/index.ts b/lib/routes/samrdprc/index.ts index 2c19525c8f..087343110f 100644 --- a/lib/routes/samrdprc/index.ts +++ b/lib/routes/samrdprc/index.ts @@ -14,7 +14,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '17'); const baseUrl = 'https://www.samrdprc.org.cn'; - const targetUrl: string = new URL(id.endsWith('/') ? id : `${id}/`, baseUrl).href; + const targetUrl = `${baseUrl}/${id.endsWith('/') ? id : `${id}/`}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/semiconductors/index.ts b/lib/routes/semiconductors/index.ts index 8c845e0ef1..98b7fe71f6 100644 --- a/lib/routes/semiconductors/index.ts +++ b/lib/routes/semiconductors/index.ts @@ -16,7 +16,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '12'); const baseUrl = 'https://www.semiconductors.org'; - const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, baseUrl).href; + const targetUrl = `${baseUrl}/${category.endsWith('/') ? category : `${category}/`}`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/sspu/pe.ts b/lib/routes/sspu/pe.ts index d9a386d600..1f9d651929 100644 --- a/lib/routes/sspu/pe.ts +++ b/lib/routes/sspu/pe.ts @@ -1,9 +1,11 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Language, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; export const route: Route = { path: '/pe/:id?', @@ -67,6 +69,10 @@ export const route: Route = { async function handler(ctx) { const { id = '342' } = ctx.req.param(); + if (!isValidHost(id)) { + throw new InvalidParameterError('Invalid id'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'https://pe2016.sspu.edu.cn'; diff --git a/lib/routes/thoughtco/index.ts b/lib/routes/thoughtco/index.ts index a12a263659..f7c1051bc5 100644 --- a/lib/routes/thoughtco/index.ts +++ b/lib/routes/thoughtco/index.ts @@ -1,9 +1,11 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { DataItem, Language, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; @@ -323,6 +325,10 @@ export const route: Route = { async function handler(ctx) { const { category = '' } = ctx.req.param(); + if (category && !isValidHost(category)) { + throw new InvalidParameterError('Invalid category'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 50; const rootUrl = 'https://www.thoughtco.com'; diff --git a/lib/routes/weibo/timeline.ts b/lib/routes/weibo/timeline.ts index f480459941..c102bfcdbb 100644 --- a/lib/routes/weibo/timeline.ts +++ b/lib/routes/weibo/timeline.ts @@ -22,7 +22,8 @@ export const route: Route = { }, { name: 'WEIBO_REDIRECT_URL', - description: '', + optional: true, + description: "OAuth callback URL. Defaults to `/weibo/timeline/0`. Set it when the auto-composed URL doesn't work", }, ], requirePuppeteer: false, @@ -93,7 +94,7 @@ async function handler(ctx) { ); // 检查token失效 if (response.error !== undefined) { - const { app_key = '', redirect_url = ctx.req.origin + '/weibo/timeline/0' } = config.weibo; + const { app_key = '', redirect_url = `${new URL(ctx.req.url).origin}/weibo/timeline/0` } = config.weibo; ctx.status = 302; ctx.set({ @@ -164,8 +165,8 @@ async function handler(ctx) { item: resultItem, }); } - if (uid === '0' || ctx.req.query()) { - const { app_key = '', redirect_url = ctx.req.origin + '/weibo/timeline/0', app_secret = '' } = config.weibo; + if (uid === '0' || ctx.req.query('code')) { + const { app_key = '', redirect_url = `${new URL(ctx.req.url).origin}/weibo/timeline/0`, app_secret = '' } = config.weibo; const code = ctx.req.query('code'); const routeParams = ctx.req.query('state'); @@ -176,14 +177,11 @@ async function handler(ctx) { const expires_in = rep.data.expires_in; await cache.set('weibotimelineuid' + uid, token, expires_in); - ctx.set({ - 'Content-Type': 'text/html; charset=UTF-8', - 'Cache-Control': 'no-cache', - }); - ctx.html(``); + ctx.header('Cache-Control', 'no-cache'); + return ctx.redirect(`/weibo/timeline/${uid}${routeParams ? `/${routeParams}` : ''}`); } } else { - const { app_key = '', redirect_url = ctx.req.origin + '/weibo/timeline/0' } = config.weibo; + const { app_key = '', redirect_url = `${new URL(ctx.req.url).origin}/weibo/timeline/0` } = config.weibo; ctx.status = 302; ctx.set({ diff --git a/lib/routes/whu/hyxt.ts b/lib/routes/whu/hyxt.ts index d4887b7f0f..978f74cd5e 100644 --- a/lib/routes/whu/hyxt.ts +++ b/lib/routes/whu/hyxt.ts @@ -61,7 +61,7 @@ async function handler(ctx) { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = `https://hyxt.${domain}`; - const currentUrl = new URL(`${category}.htm`, rootUrl).href; + const currentUrl = `${rootUrl}/${category}.htm`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/xjtu/zs.ts b/lib/routes/xjtu/zs.ts index dda1312587..f352bda59d 100644 --- a/lib/routes/xjtu/zs.ts +++ b/lib/routes/xjtu/zs.ts @@ -15,7 +15,7 @@ export const handler = async (ctx: Context): Promise => { const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'https://zs.xjtu.edu.cn'; - const targetUrl: string = new URL(`${category}.htm`, baseUrl).href; + const targetUrl = `${baseUrl}/${category}.htm`; const response = await ofetch(targetUrl); const $: CheerioAPI = load(response); diff --git a/lib/routes/xmnn/news.ts b/lib/routes/xmnn/news.ts index 16a6ce3b96..b773872081 100644 --- a/lib/routes/xmnn/news.ts +++ b/lib/routes/xmnn/news.ts @@ -46,7 +46,7 @@ async function handler(ctx) { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'https://news.xmnn.cn'; - const currentUrl = new URL(`${category}/`, rootUrl).href; + const currentUrl = `${rootUrl}/${category}/`; const { data: response } = await got(currentUrl); diff --git a/lib/routes/yangtzeu/dongke.ts b/lib/routes/yangtzeu/dongke.ts index 2cd9f38c97..3632a24ffa 100644 --- a/lib/routes/yangtzeu/dongke.ts +++ b/lib/routes/yangtzeu/dongke.ts @@ -25,7 +25,7 @@ async function handler(ctx): Promise { const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10; const rootUrl = 'https://dongke.yangtzeu.edu.cn'; - const currentUrl = new URL(`/${ctx.req.param('path') ?? 'yqzl/xyxw'}.htm`, rootUrl).href; + const currentUrl = `${rootUrl}/${ctx.req.param('path') ?? 'yqzl/xyxw'}.htm`; const { data: response } = await got(currentUrl); @@ -52,15 +52,11 @@ async function handler(ctx): Promise { item.title = content('title').text(); item.description = content('div.v_news_content').html(); - item.category = content('meta[name="keywords"]').prop('content').split(','); - item.pubDate = timezone( - parseDate( - content('p.content-info') - .text() - .match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)![0] - ), - 8 - ); + item.category = content('meta[name="keywords"]').prop('content')?.split(','); + const pubDate = content('p.content-info') + .text() + .match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)?.[0]; + item.pubDate = pubDate ? timezone(parseDate(pubDate), 8) : undefined; return item; }) diff --git a/lib/routes/yicai/dt.ts b/lib/routes/yicai/dt.ts index 76cb760c9d..641cb4a838 100644 --- a/lib/routes/yicai/dt.ts +++ b/lib/routes/yicai/dt.ts @@ -1,9 +1,11 @@ import { load } from 'cheerio'; +import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Language, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; +import { isValidHost } from '@/utils/valid-host'; import { renderDescription } from './templates/description'; @@ -74,6 +76,10 @@ export const route: Route = { async function handler(ctx) { const { column = 'article', category = '0' } = ctx.req.param(); + if (!isValidHost(column)) { + throw new InvalidParameterError('Invalid column'); + } + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'https://dt.yicai.com'; diff --git a/lib/routes/yna/index.ts b/lib/routes/yna/index.ts index 8997d9bb51..9ed13ca14c 100644 --- a/lib/routes/yna/index.ts +++ b/lib/routes/yna/index.ts @@ -7,6 +7,7 @@ import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import parser from '@/utils/rss-parser'; import timezone from '@/utils/timezone'; +import { isValidHost } from '@/utils/valid-host'; export const route: Route = { path: '/:lang?/:channel?', @@ -48,17 +49,12 @@ For example, the path for the RSS feed url