feat(config)!: unsafe domain toggle (#11588)

This commit is contained in:
Tony
2023-01-10 11:45:05 +00:00
committed by GitHub
parent ab2f61824d
commit a66cbcf6ee
66 changed files with 338 additions and 15 deletions

View File

@@ -564,7 +564,7 @@ It is also valid to contain route parameters, e.g. `/weibo/user/2612249974`.
::: tip Experimental features
Configs in this sections are in beta stage, and are turn off by default. Please read corresponded description and turn on if necessary.
Configs in this sections are in beta stage, and **are turn off by default**. Please read corresponded description and turn on if necessary.
:::
@@ -572,6 +572,8 @@ Configs in this sections are in beta stage, and are turn off by default. Please
`FILTER_REGEX_ENGINE`: Define Regex engine used in [Parameters->filtering](/en/parameter.html#filtering). Valid value are `[re2, regexp]`. Default value is `re2`. We suggest public instance should leave this value to default, and this option right now is mainly for backward compatibility.
`ALLOW_USER_SUPPLY_UNSAFE_DOMAIN`: allow users to provide a domain as a parameter to routes that are not in their allow list, respectively. Public instances are suggested to leave this value default, as it may lead to [Server-Side Request Forgery (SSRF)](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery)
### Other Application Configurations
`DISALLOW_ROBOT`: prevent indexing by search engine, default to enable, set false or 0 to disable

View File

@@ -571,7 +571,7 @@ RSSHub 支持使用访问密钥 / 码,白名单和黑名单三种方式进行
::: tip 测试特性
这个板块控制的是一些新特性的选项,默认他们都是关闭的。如果有需要请阅读对应说明后按需开启
这个板块控制的是一些新特性的选项,他们都是**默认关闭**的。如果有需要请阅读对应说明后按需开启
:::
@@ -579,6 +579,8 @@ RSSHub 支持使用访问密钥 / 码,白名单和黑名单三种方式进行
`FILTER_REGEX_ENGINE`: 控制 [通用参数 -> 内容过滤](/parameter.html#nei-rong-guo-lu) 使用的正则引擎。可选`[re2, regexp]`,默认`re2`。我们推荐公开实例不要调整这个选项,这个选项目前主要用于向后兼容。
`ALLOW_USER_SUPPLY_UNSAFE_DOMAIN`: 允许用户为路由提供域名作为参数。建议公共实例不要调整此选项,开启后可能会导致 [服务端请求伪造SSRF](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery)
### 其他应用配置
`DISALLOW_ROBOT`: 阻止搜索引擎收录,默认开启,设置 false 或 0 关闭

View File

@@ -97,6 +97,7 @@ const calculateValue = () => {
feature: {
allow_user_hotlink_template: envs.ALLOW_USER_HOTLINK_TEMPLATE === 'true',
filter_regex_engine: envs.FILTER_REGEX_ENGINE || 're2',
allow_user_supply_unsafe_domain: envs.ALLOW_USER_SUPPLY_UNSAFE_DOMAIN === 'true',
},
suffix: envs.SUFFIX,
titleLengthLimit: parseInt(envs.TITLE_LENGTH_LIMIT) || 150,

View File

@@ -1,9 +1,13 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const lang = ctx.params.lang || 'en';
const id = ctx.params.id || 'bandizip';
if (!isValidHost(lang)) {
throw Error('Invalid language code');
}
const rootUrl = `https://${lang}.bandisoft.com`;
const currentUrl = `${rootUrl}/${id}/history/`;

View File

@@ -1,7 +1,12 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
if (!isValidHost(ctx.params.column)) {
throw Error('Invalid column');
}
const url = `http://${ctx.params.column}.bio1000.com/${ctx.params.id}`;
const res = await got.get(url);
const $ = cheerio.load(res.data);

View File

@@ -4,9 +4,13 @@ const md = require('markdown-it')({
html: true,
});
const dayjs = require('dayjs');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const type = ctx.params.type;
if (!isValidHost(type)) {
throw Error('Invalid type');
}
const url = `https://${type}.hedwig.pub`;
const res = await got({

View File

@@ -1,7 +1,12 @@
const parser = require('@/utils/rss-parser');
const config = require('@/config').value;
const allowDomain = ['lawrence.code.blog'];
module.exports = async (ctx) => {
if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.includes(ctx.params.domain)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const scheme = ctx.params.https || 'https';
const cdn = config.wordpress.cdnUrl;

View File

@@ -1,10 +1,13 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
const maxPages = 5;
module.exports = async (ctx) => {
const { subdomain } = ctx.params;
if (!isValidHost(subdomain)) {
throw Error('Invalid subdomain');
}
const shopUrl = `https://${subdomain}.booth.pm`;
let shopName;

View File

@@ -1,6 +1,7 @@
const got = require('@/utils/got');
const parser = require('@/utils/rss-parser');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
async function load(link, need_feed_description) {
const response = await got.get(link);
@@ -29,6 +30,9 @@ async function load(link, need_feed_description) {
module.exports = async (ctx) => {
const { column } = ctx.params;
if (!isValidHost(column)) {
throw Error('Invalid column');
}
const link = `http://${column}.blog.caixin.com`;
const feed_url = `${link}/feed`;
const feed = await parser.parseURL(feed_url);

View File

@@ -1,10 +1,14 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const category = ctx.params.category;
const column = ctx.params.column;
const url = `http://${column}.caixin.com/${category}`;
if (!isValidHost(column)) {
throw Error('Invalid column');
}
const response = await got({
method: 'get',

View File

@@ -1,7 +1,12 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
if (!isValidHost(ctx.params.location)) {
throw Error('Invalid location');
}
const queryParams = ctx.request.querystring;
const queryUrl = `https://${ctx.params.location}.craigslist.org/search/${ctx.params.type}?${queryParams}`;
const { data } = await got.get(queryUrl);

View File

@@ -1,9 +1,13 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const parser = require('@/utils/rss-parser');
const allowLang = ['chinese', 'cn', 'us', 'japanese', 'www'];
module.exports = async (ctx) => {
const lang = ctx.params.lang === 'us' ? 'www' : ctx.params.lang || 'cn';
if (!allowLang.includes(lang)) {
throw Error('Invalid lang');
}
const rssUrl = `https://${lang}.engadget.com/rss.xml`;
const feed = await parser.parseURL(rssUrl);

View File

@@ -4,12 +4,15 @@
// user?: fanbox domain name
const got = require('@/utils/got');
const { isValidHost } = require('@/utils/valid-host');
const conv_item = require('./conv');
const get_header = require('./header');
module.exports = async (ctx) => {
const user = ctx.params.user || 'official'; // if no user specified, just go to official page
if (!isValidHost(user)) {
throw Error('Invalid user');
}
const box_url = `https://${user}.fanbox.cc`;
// get user info

View File

@@ -1,8 +1,12 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const country = ctx.params.country || 'ww';
if (!isValidHost(country)) {
throw Error('Invalid country');
}
const rootUrl = `https://${country}.fashionnetwork.com`;
const response = await got({

View File

@@ -1,5 +1,6 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const country = ctx.params.country || 'ww';
@@ -12,6 +13,10 @@ module.exports = async (ctx) => {
const sectorsUrl = sectors ? 'sectors%5B%5D=' + sectors.split(',').join('&sectors%5B%5D=') : '';
const categoriesUrl = categories ? 'categs%5B%5D=' + categories.split(',').join('&categs%5B%5D=') : '';
if (!isValidHost(country)) {
throw Error('Invalid country');
}
const rootUrl = `https://${country}.fashionnetwork.com`;
const currentUrl = `${rootUrl}/news/s.jsonp?${sectorsUrl}&${categoriesUrl}`;
const response = await got({

View File

@@ -0,0 +1,5 @@
const allowHost = ['gitlab.com'];
module.exports = {
allowHost,
};

View File

@@ -1,5 +1,7 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const config = require('@/config').value;
const { allowHost } = require('./common');
module.exports = async (ctx) => {
let { type, host } = ctx.params;
@@ -10,6 +12,9 @@ module.exports = async (ctx) => {
starred: 'Most stars',
all: 'All',
};
if (!config.feature.allow_user_supply_unsafe_domain && !allowHost.includes(new URL(host).hostname)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const res = await got({
method: 'get',

View File

@@ -1,8 +1,13 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const config = require('@/config').value;
const { allowHost } = require('./common');
module.exports = async (ctx) => {
const { namespace, project, host } = ctx.params;
if (!config.feature.allow_user_supply_unsafe_domain && !allowHost.includes(host)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const host_ = host ? host : 'gitlab.com';
const namespace_ = encodeURIComponent(namespace);

View File

@@ -1,8 +1,13 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const config = require('@/config').value;
const { allowHost } = require('./common');
module.exports = async (ctx) => {
const { namespace, project, host } = ctx.params;
if (!config.feature.allow_user_supply_unsafe_domain && !allowHost.includes(host)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const host_ = host ? host : 'gitlab.com';
const namespace_ = encodeURIComponent(namespace);

View File

@@ -1,7 +1,11 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
const config = require('@/config').value;
module.exports = async (ctx) => {
if (!config.feature.allow_user_supply_unsafe_domain) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const url = `http://${ctx.params.url}`;
const res = await got.get(`${url}/archives/`);
const $ = cheerio.load(res.data);

View File

@@ -1,7 +1,11 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
const config = require('@/config').value;
module.exports = async (ctx) => {
if (!config.feature.allow_user_supply_unsafe_domain) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const url = `http://${ctx.params.url}`;
const res = await got.get(`${url}/archives/`);
const $ = cheerio.load(res.data);

View File

@@ -1,7 +1,11 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
const config = require('@/config').value;
module.exports = async (ctx) => {
if (!config.feature.allow_user_supply_unsafe_domain) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const url = `http://${ctx.params.url}`;
const res = await got.get(url);
const $ = cheerio.load(res.data);

View File

@@ -1,9 +1,13 @@
const utils = require('./utils');
const config = require('@/config').value;
module.exports = async (ctx) => {
const site = ctx.params.site;
const account_id = ctx.params.account_id;
const only_media = ctx.params.only_media ? 'true' : 'false';
if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const { account_data, data } = await utils.getAccountStatuses(site, account_id, only_media);

View File

@@ -1,9 +1,13 @@
const got = require('@/utils/got');
const utils = require('./utils');
const config = require('@/config').value;
module.exports = async (ctx) => {
const site = ctx.params.site;
const only_media = ctx.params.only_media ? 'true' : 'false';
if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const url = `http://${site}/api/v1/timelines/public?local=true&only_media=${only_media}`;

View File

@@ -1,9 +1,13 @@
const got = require('@/utils/got');
const utils = require('./utils');
const config = require('@/config').value;
module.exports = async (ctx) => {
const site = ctx.params.site;
const only_media = ctx.params.only_media ? 'true' : 'false';
if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const url = `http://${site}/api/v1/timelines/public?remote=true&only_media=${only_media}`;

View File

@@ -1,6 +1,8 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const allowSiteList = ['mastodon.social', 'pawoo.net'];
const parseStatuses = (data) =>
data.map((item) => {
// docs on: https://docs.joinmastodon.org/entities/status/
@@ -125,4 +127,5 @@ module.exports = {
parseStatuses,
getAccountStatuses,
getAccountIdByAcct,
allowSiteList,
};

View File

@@ -1,10 +1,14 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const language = ctx.params.language || 'www';
const url = ctx.params.url || 'video';
const link = `https://${language}.pornhub.com/${url}`;
if (!isValidHost(language)) {
throw Error('Invalid language');
}
const response = await got.get(link);
const $ = cheerio.load(response.data);

View File

@@ -1,11 +1,15 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const language = ctx.params.language || 'www';
const username = ctx.params.username;
const sort = ctx.params.sort || 'mr';
const link = `https://${language}.pornhub.com/model/${username}/videos?o=${sort}`;
if (!isValidHost(language)) {
throw Error('Invalid language');
}
const response = await got.get(link);
const $ = cheerio.load(response.data);

View File

@@ -1,11 +1,15 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const language = ctx.params.language || 'www';
const username = ctx.params.username;
const sort = ctx.params.sort || 'mr';
const link = `https://${language}.pornhub.com/pornstar/${username}/videos?o=${sort}`;
if (!isValidHost(language)) {
throw Error('Invalid language');
}
const response = await got.get(link);
const $ = cheerio.load(response.data);

View File

@@ -1,10 +1,14 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const language = ctx.params.language || 'www';
const username = ctx.params.username;
const link = `https://${language}.pornhub.com/users/${username}/videos`;
if (!isValidHost(language)) {
throw Error('Invalid language');
}
const response = await got.get(link);
const $ = cheerio.load(response.data);

View File

@@ -11,6 +11,9 @@ const getBaseUrl = (language) => (language === 'ja' ? 'https://touhougarakuta.co
module.exports = async (ctx) => {
const { language, type } = ctx.params;
if (!Object.keys(languageCodes).includes(language)) {
throw Error('Invalid language');
}
const baseUrl = getBaseUrl(language);

View File

@@ -1,13 +1,17 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
ctx.params.lang = ctx.params.lang || 'www';
ctx.params.platform = ctx.params.platform || '';
ctx.params.year = ctx.params.year || '';
const lang = ctx.params.lang || 'www';
const platform = ctx.params.platform || '';
const year = ctx.params.year || '';
if (!isValidHost(lang)) {
throw Error('Invalid lang');
}
const rootUrl = `https://${ctx.params.lang === 'en' ? 'www' : ctx.params.lang}.weforum.org`;
const currentUrl = `${rootUrl}/reports?platform=${ctx.params.platform}&year=${ctx.params.year}`;
const rootUrl = `https://${lang === 'en' ? 'www' : lang}.weforum.org`;
const currentUrl = `${rootUrl}/reports?platform=${platform}&year=${year}`;
const response = await got({
method: 'get',
url: currentUrl,

View File

@@ -1,9 +1,13 @@
const got = require('@/utils/got');
const parser = require('@/utils/rss-parser');
const cheerio = require('cheerio');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const region = ctx.params.region === 'en' ? '' : ctx.params.region.toLowerCase() + '.';
if (!isValidHost(region)) {
throw Error('Invalid region');
}
const category = ctx.params.category ? ctx.params.category.toLowerCase() : '';
const rssUrl = `https://${region}news.yahoo.com/rss/${category}`;
const feed = await parser.parseURL(rssUrl);

View File

@@ -1,4 +1,5 @@
const got = require('@/utils/got');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const city = ctx.params.city || 'sh';
@@ -7,6 +8,10 @@ module.exports = async (ctx) => {
const room = ctx.params.room || '1';
const domain = `${city === 'bj' ? '' : city + '.'}m.ziroom.com`;
if (!isValidHost(city)) {
throw Error('Invalid city');
}
const response = await got({
method: 'post',
url: `http://${domain}/list/ajax-get-data`,

16
lib/utils/valid-host.js Normal file
View File

@@ -0,0 +1,16 @@
/**
* Check if a sub-domain is valid
* @param {String} hostname sub-domain
* @returns {Boolean} true if valid
*/
const isValidHost = (hostname) => {
if (typeof hostname !== 'string') {
return false;
}
const regex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
return regex.test(hostname);
};
module.exports = {
isValidHost,
};

View File

@@ -3,6 +3,7 @@ const cheerio = require('cheerio');
const timezone = require('@/utils/timezone');
const { parseDate } = require('@/utils/parse-date');
const iconv = require('iconv-lite');
const { isValidHost } = require('@/utils/valid-host');
const setCookie = function (cookieName, cookieValue, seconds, path, domain, secure) {
let expires = null;
@@ -15,6 +16,9 @@ const setCookie = function (cookieName, cookieValue, seconds, path, domain, secu
module.exports = async (ctx) => {
const city = ctx.params.city ?? 'www';
if (!isValidHost(city)) {
throw Error('Invalid city');
}
const rootUrl = `https://${city}.19lou.com`;

View File

@@ -3,11 +3,13 @@ const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const { domainValidation } = require('./utils');
module.exports = async (ctx) => {
const { domain = '91porn.com' } = ctx.query;
const { uid, lang = 'en_US' } = ctx.params;
const siteUrl = `https://${domain}/uvideos.php?UID=${uid}&type=public`;
domainValidation(domain, ctx);
const response = await got.post(siteUrl, {
form: {

View File

@@ -3,11 +3,13 @@ const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const { domainValidation } = require('./utils');
module.exports = async (ctx) => {
const { domain = '91porn.com' } = ctx.query;
const siteUrl = `https://${domain}/index.php`;
const { lang = 'en_US' } = ctx.params;
domainValidation(domain, ctx);
const response = await got.post(siteUrl, {
form: {

12
lib/v2/91porn/utils.js Normal file
View File

@@ -0,0 +1,12 @@
const config = require('@/config').value;
const allowDomain = ['91porn.com', 'www.91porn.com', '0122.91p30.com', 'www.91zuixindizhi.com', 'w1218.91p46.com'];
const domainValidation = (domain, ctx) => {
if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.includes(domain)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
};
module.exports = {
domainValidation,
};

View File

@@ -2,9 +2,13 @@ const got = require('@/utils/got');
const cheerio = require('cheerio');
const timezone = require('@/utils/timezone');
const { parseDate } = require('@/utils/parse-date');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const city = ctx.params.city;
if (!isValidHost(city)) {
throw Error('Invalid city');
}
const rootUrl = `http://${city}.bendibao.com`;

View File

@@ -3,10 +3,31 @@ const cheerio = require('cheerio');
const iconv = require('iconv-lite');
const timezone = require('@/utils/timezone');
const { parseDate } = require('@/utils/parse-date');
const config = require('@/config').value;
const allowHost = [
'www.xbiquwx.la',
'www.biqu5200.net',
'www.xbiquge.so',
'www.biqugeu.net',
'www.b520.cc',
'www.ahfgb.com',
'www.ibiquge.la',
'www.biquge.tv',
'www.bswtan.com',
'www.biquge.co',
'www.bqzhh.com',
'www.biqugse.com',
'www.ibiquge.info',
'www.ishuquge.com',
'www.mayiwxw.com',
];
module.exports = async (ctx) => {
const rootUrl = ctx.path.split('/').slice(1, 4).join('/');
const currentUrl = ctx.path.slice(1);
if (!config.feature.allow_user_supply_unsafe_domain && !allowHost.includes(new URL(rootUrl).hostname)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const response = await got({
method: 'get',

View File

@@ -4,10 +4,15 @@ const timezone = require('@/utils/timezone');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const config = require('@/config').value;
const allowDomain = ['btbtt15.com'];
module.exports = async (ctx) => {
let category = ctx.params.category ?? '';
let domain = ctx.query.domain ?? 'btbtt20.com';
let domain = ctx.query.domain ?? 'btbtt15.com';
if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.includes(new URL(domain).hostname)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
if (category === 'base') {
category = '';

View File

@@ -12,6 +12,9 @@ const categories = {
module.exports = async (ctx) => {
const category = ctx.params.category ?? 'jxrb';
const id = ctx.params.id;
if (!Object.keys(categories).includes(category)) {
throw Error('Invalid category');
}
const rootUrl = `https://${category}.cnjxol.com`;
const currentUrl = `${rootUrl}/${category}Paper/pc/layout`;

View File

@@ -3,9 +3,13 @@ const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const defaults = require('./defaults');
const shortcuts = require('./shortcuts');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const site = ctx.params[0] ?? 'news';
if (!isValidHost(site)) {
throw Error('Invalid site');
}
let items;
let category = ctx.params[1] ?? (defaults.hasOwnProperty(site) ? defaults[site] : '');

View File

@@ -1,16 +1,20 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { isValidHost } = require('@/utils/valid-host');
const cateList = ['all', 'design-resources', 'learn-design', 'inside-eagle'];
module.exports = async (ctx) => {
let cate = ctx.params.cate ?? 'all';
let language = ctx.params.language ?? 'cn';
if (cateList.indexOf(cate) === -1) {
if (!isValidHost(cate) || !isValidHost(language)) {
throw Error('Invalid host');
}
if (!cateList.includes(cate)) {
language = cate;
cate = 'all';
}
const host = `https://${language}.eagle.cool`;
const url = `${host}/blog/${cate === 'all' ? '' : cate}`;

View File

@@ -4,9 +4,13 @@ const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const allowRegion = ['tw', 'hk'];
module.exports = async (ctx) => {
const region = ctx.params.region ?? 'tw';
if (!allowRegion.includes(region)) {
throw Error('Invalid region');
}
const feed = await parser.parseURL(`https://www.eprice.com.${region}/news/rss.xml`);

View File

@@ -2,11 +2,16 @@ const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const lang = ctx.params.lang;
const type = ctx.params.type ?? 'all';
if (!isValidHost(lang)) {
throw Error('Invalid lang');
}
const response = await got({
method: 'get',
url: `https://lodestonenews.com/news/${type}?locale=${lang}`,

View File

@@ -1,9 +1,13 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const parser = require('@/utils/rss-parser');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const { domain = 'news', category } = ctx.params;
if (!isValidHost(domain)) {
throw Error('Invalid domain');
}
const baseUrl = `https://${domain}.gamme.com.tw`;
const feed = await parser.parseURL(`${baseUrl + (category ? `/category/${category}` : '')}/feed`);

View File

@@ -1,9 +1,13 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const { domain = 'news', tag } = ctx.params;
if (!isValidHost(domain)) {
throw Error('Invalid domain');
}
const baseUrl = `https://${domain}.gamme.com.tw`;
const pageUrl = `${baseUrl}/tag/${tag}`;

View File

@@ -2,10 +2,14 @@ const got = require('@/utils/got');
const cheerio = require('cheerio');
const { art } = require('@/utils/render');
const path = require('path');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const username = ctx.params.username;
const products = ctx.params.products;
if (!isValidHost(username)) {
throw Error('Invalid username');
}
const url = `https://${username}.gumroad.com/l/${products}`;
const response = await got(url);

View File

@@ -1,6 +1,7 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { isValidHost } = require('@/utils/valid-host');
function getKeysRecursive(dic, key, attr, array) {
Object.values(dic).forEach((v) => {
@@ -15,6 +16,10 @@ function getKeysRecursive(dic, key, attr, array) {
module.exports = async (ctx) => {
const category = ctx.params.category ?? 'china';
if (!isValidHost(category)) {
throw Error('Invalid category');
}
const host = 'https://' + category + '.huanqiu.com';
const resp = await got({

View File

@@ -4,10 +4,14 @@ const timezone = require('@/utils/timezone');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const user = ctx.params.user ?? '';
const id = ctx.params.id ?? '';
if (!isValidHost(user)) {
throw Error('Invalid user');
}
const rootUrl = `https://${user}.itch.io/${id}/devlog`;

View File

@@ -3,17 +3,27 @@ const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const config = require('@/config').value;
const toSize = (raw) => {
const matches = raw.match(/(\d+(\.\d+)?)(\w+)/);
return matches[3] === 'GB' ? matches[1] * 1024 : matches[1];
};
const allowDomain = ['javbus.com', 'javbus.org', 'javsee.icu', 'javsee.one'];
module.exports = async (ctx) => {
const isWestern = /^\/western/.test(ctx.path);
const domain = ctx.query.domain ?? 'javbus.com';
const westernDomain = ctx.query.western_domain ?? 'javbus.org';
const rootUrl = `https://www.${domain}`;
const westernUrl = `https://www.${westernDomain}`;
if (!config.feature.allow_user_supply_unsafe_domain && (!allowDomain.includes(new URL(domain).hostname) || !allowDomain.includes(new URL(westernDomain).hostname))) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const rootUrl = `https://www.${ctx.query.domain ?? 'javbus.com'}`;
const westernUrl = `https://www.${ctx.query.western_domain ?? 'javbus.org'}`;
const currentUrl = `${isWestern ? westernUrl : rootUrl}${ctx.path.replace(/^\/western/, '').replace(/\/home/, '')}`;
const response = await got({

View File

@@ -1,10 +1,16 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const config = require('@/config').value;
const allowDomain = ['javdb.com', 'javdb36.com', 'javdb007.com'];
module.exports = {
ProcessItems: async (ctx, currentUrl, title) => {
const domain = ctx.query.domain ?? 'javdb.com';
if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.includes(new URL(domain).hostname)) {
ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
const rootUrl = `https://${domain}`;
const response = await got({

View File

@@ -1,9 +1,13 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const name = ctx.params.name ?? 'i';
const limit = ctx.query.limit ? parseInt(ctx.query.limit) : '50';
if (!isValidHost(name)) {
throw Error('Invalid name');
}
const rootUrl = `${name}.lofter.com`;

View File

@@ -4,10 +4,13 @@ const md = require('markdown-it')({
html: true,
linkify: true,
});
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const id = ctx.params.id;
if (!id.endsWith('.eth') && !isValidHost(id)) {
throw Error('Invalid id');
}
const rootUrl = 'https://mirror.xyz';
const currentUrl = id.endsWith('.eth') ? `${rootUrl}/${id}` : `https://${id}.mirror.xyz`;

View File

@@ -4,6 +4,7 @@ const timezone = require('@/utils/timezone');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const category = ctx.params.category ?? '-1';
@@ -13,6 +14,10 @@ module.exports = async (ctx) => {
const latestAlerts = ctx.params.latestAlerts ?? '1';
const latestPictures = ctx.params.latestPictures ?? '1';
if (language && !isValidHost(language)) {
throw Error('Invalid language');
}
const rootUrl = `https://${language === 'en' || language === '' ? '' : `${language}.`}myfigurecollection.net`;
const currentUrl = `${rootUrl}/browse.v4.php?mode=activity&latestAdditions=${latestAdditions}&latestEdits=${latestEdits}&latestAlerts=${latestAlerts}&latestPictures=${latestPictures}&rootId=${category}`;

View File

@@ -2,6 +2,7 @@ const got = require('@/utils/got');
const cheerio = require('cheerio');
const { art } = require('@/utils/render');
const path = require('path');
const { isValidHost } = require('@/utils/valid-host');
const shortcuts = {
potd: 'picture/browse/potd/',
@@ -12,6 +13,9 @@ const shortcuts = {
module.exports = async (ctx) => {
const language = ctx.params.language ?? '';
const category = ctx.params.category ?? 'figure';
if (language && !isValidHost(language)) {
throw Error('Invalid language');
}
const rootUrl = `https://${language === 'en' || language === '' ? '' : `${language}.`}myfigurecollection.net`;
const currentUrl = `${rootUrl}/${shortcuts.hasOwnProperty(category) ? shortcuts[category] : category}`;

View File

@@ -2,6 +2,7 @@ const got = require('@/utils/got');
const cheerio = require('cheerio');
const timezone = require('@/utils/timezone');
const { parseDate } = require('@/utils/parse-date');
const { isValidHost } = require('@/utils/valid-host');
const cleanContent = (language, content) => {
switch (language) {
@@ -23,6 +24,9 @@ module.exports = async (ctx) => {
const language = ctx.params.language ?? 'cn';
const category = ctx.params.category ?? '';
const type = ctx.params.type ?? '';
if (!isValidHost(language)) {
throw Error('Invalid language');
}
const rootUrl = `https://${language === 'zh' ? `zh.cn` : language}.nikkei.com`;
const currentUrl = `${rootUrl}/${category ? (category === 'rss' ? 'rss.html' : `${category}${type ? `/${type}` : ''}.html`) : ''}`;

View File

@@ -3,12 +3,16 @@ const cheerio = require('cheerio');
const iconv = require('iconv-lite');
const timezone = require('@/utils/timezone');
const { parseDate } = require('@/utils/parse-date');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const site = ctx.params[0] ?? 'www';
let category = ctx.params[1] ?? (site === 'www' ? '59476' : '');
category = site === 'cpc' && category === '24h' ? '87228' : category;
if (!isValidHost(site)) {
throw Error('Invalid site');
}
const rootUrl = `http://${site}.people.com.cn`;
const currentUrl = `${rootUrl}/GB/${category}/index.html`;

View File

@@ -1,12 +1,16 @@
const cheerio = require('cheerio');
const { puppeteerGet, renderDesc } = require('./utils');
const config = require('@/config').value;
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const pub = ctx.params.pub;
const jrn = ctx.params.jrn;
const host = `https://${pub}.scitation.org`;
const jrnlUrl = `${host}/toc/${jrn}/current?size=all`;
if (!isValidHost(pub)) {
throw Error('Invalid pub');
}
// use Puppeteer due to the obstacle by cloudflare challenge
const browser = await require('@/utils/puppeteer')();

View File

@@ -1,6 +1,7 @@
const cheerio = require('cheerio');
const { puppeteerGet, renderDesc } = require('./utils');
const config = require('@/config').value;
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const pub = ctx.params.pub;
@@ -8,6 +9,9 @@ module.exports = async (ctx) => {
const sec = ctx.params.sec.split('+').join(' ');
const host = `https://${pub}.scitation.org`;
const jrnlUrl = `${host}/toc/${jrn}/current?size=all`;
if (!isValidHost(pub)) {
throw Error('Invalid pub');
}
// use Puppeteer due to the obstacle by cloudflare challenge
const browser = await require('@/utils/puppeteer')();

View File

@@ -6,9 +6,14 @@
const got = require('@/utils/got'); // get web content
const cheerio = require('cheerio'); // html parser
const get_article = require('./_article');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const type = ctx.params.type ?? 'www';
if (!isValidHost(type)) {
throw Error('Invalid type');
}
const base_url = `https://${type}.solidot.org`;
const response = await got({
method: 'get',

View File

@@ -2,11 +2,15 @@ const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { extractArticle, extractWork } = require('./utils');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const { uid } = ctx.params;
let pageUrl = `https://www.zcool.com.cn/u/${uid}`;
if (isNaN(uid)) {
if (!isValidHost(uid)) {
throw Error('Invalid uid');
}
pageUrl = `https://${uid}.zcool.com.cn`;
}
const { data: response } = await got(pageUrl);

View File

@@ -1,9 +1,13 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const { isValidHost } = require('@/utils/valid-host');
module.exports = async (ctx) => {
const { id } = ctx.params;
const limit = ctx.query.limit ? parseInt(ctx.query.limit) : 20;
if (!isValidHost(id)) {
throw Error('Invalid id');
}
const response = await got({
method: 'get',

21
test/utils/valid-host.js Normal file
View File

@@ -0,0 +1,21 @@
const { isValidHost } = require('../../lib/utils/valid-host');
describe('valid-host', () => {
it('validate hostname', () => {
expect(isValidHost()).toBe(false);
expect(isValidHost(123)).toBe(false);
expect(isValidHost('')).toBe(false);
expect(isValidHost('subd0main')).toBe(true);
expect(isValidHost('-subd0main')).toBe(false);
expect(isValidHost('sub-d0main')).toBe(true);
expect(isValidHost('subd0main-')).toBe(false);
expect(isValidHost('sub.d0main')).toBe(false);
expect(isValidHost('sub-.d0main')).toBe(false);
expect(isValidHost('s')).toBe(true);
expect(isValidHost('-')).toBe(false);
expect(isValidHost('0')).toBe(true);
expect(isValidHost('s-')).toBe(false);
expect(isValidHost('s-u')).toBe(true);
expect(isValidHost('su')).toBe(true);
});
});