mirror of
https://github.com/DIYgod/RSSHub.git
synced 2025-12-05 12:21:31 +08:00
feat(core): anti-hotlink experimental parameters (#9997)
Co-authored-by: Rongrong <i@rong.moe>
This commit is contained in:
@@ -8,9 +8,13 @@
|
||||
|
||||
**A:** [rsshub.app](https://rsshub.app) is the demo instance provided, running the latest build of RSSHub from master branch, the cache is set 120 minutes and it's free to use. However, if you see an badge <Badge text="strict anti-crawler policy" vertical="middle" type="warn"/> for route, this means popular websites such as Facebook etc. may pose a request quota on individual IP address, which means it can get unreliable from time to time for the demo instance. You are encouraged to [host your own RSSHub instance](/en/install/) to get a better usability.
|
||||
|
||||
**Q: Why are images not loading in some RSSHub routes?**
|
||||
**Q: Why are images/videos not loading in some RSSHub routes?**
|
||||
|
||||
**A:** RSSHub fetches and respects the original image URLs from original sites, `referrerpolicy="no-referrer"` attribute is added to all images to solve the issues caused by cross-domain requests. Third party RSS service providers such as Feedly and Inoreader, strip this attribute off which leads to cross-domain requests being blocked.
|
||||
**A:** RSSHub fetches and respects the original image/video URLs from original sites, in which some are behind anti-hotlink filters. `referrerpolicy="no-referrer"` attribute is added to all images to solve the issues caused by cross-domain requests. Third party RSS service providers such as Feedly and Inoreader, strip this attribute off, resulting in cross-domain requests being blocked. Meanwhile, the attribute is not available for videos yet, resulting in most RSS readers unable to pass the anti-hotlink check. Here are some workarounds:
|
||||
|
||||
1. Migrate to RSS readers that do not send Referer,such as [Inoreader for Web](https://www.inoreader.com/) with a [user script disabling Referer](https://greasyfork.org/en/scripts/376884), [RSS to Telegram Bot](https://github.com/Rongronggg9/RSS-to-Telegram-Bot), etc. If your RSS reader can bypass the anti-hotlink check successfully and play embedded videos, it's an RSS reader that do not send Referer. Please consider adding it to the documentation to help more people.
|
||||
2. Set up a reverse proxy, refer to [Parameters->Multimedia processing](/en/parameter.html#multimedia-processing) for more details.
|
||||
3. Navigate back to the original site.
|
||||
|
||||
**Q: The website I want is not supported QAQ**
|
||||
|
||||
|
||||
@@ -523,7 +523,13 @@ See the relation between access key/code and white/blacklisting.
|
||||
|
||||
### Image Processing
|
||||
|
||||
`HOTLINK_TEMPLATE`: replace image URL in the description to avoid anti-hotlink protection, leave it blank to disable this function. Usage reference [#2769](https://github.com/DIYgod/RSSHub/issues/2769). You may use any property listed in [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL#Properties), format of JS template literal. e.g. `${protocol}//${host}${pathname}`, `https://i3.wp.com/${host}${pathname}`
|
||||
::: warning Deprecation warning
|
||||
|
||||
The options below are deprecated, preserved only for backward compatibility, please refer to [Parameters->Multimedia processing](/en/parameter.html#multimedia-processing) for more details.
|
||||
|
||||
:::
|
||||
|
||||
`HOTLINK_TEMPLATE`: replace image URL in the description to avoid anti-hotlink protection, leave it blank to disable this function. Usage reference [#2769](https://github.com/DIYgod/RSSHub/issues/2769). You may use any property listed in [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL#Properties) (suffixing with `_ue` results in URL encoding), format of JS template literal. e.g. `${protocol}//${host}${pathname}`, `https://i3.wp.com/${host}${pathname}`, `https://images.weserv.nl?url=${href_ue}`
|
||||
|
||||
`HOTLINK_INCLUDE_PATHS`: limit the routes to be processed, only matched routes will be processed. Set multiple values with comma `,` as delimiter. If not set, all routes will be processed
|
||||
|
||||
@@ -539,6 +545,16 @@ It is also valid to contain route parameters, e.g. `/weibo/user/2612249974`.
|
||||
|
||||
:::
|
||||
|
||||
### Features
|
||||
|
||||
::: 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.
|
||||
|
||||
:::
|
||||
|
||||
`ALLOW_USER_HOTLINK_TEMPLATE`: [Parameters->Multimedia processing](/en/parameter.html#multimedia-processing)
|
||||
|
||||
### Other Application Configurations
|
||||
|
||||
`DISALLOW_ROBOT`: prevent indexing by search engine, default to enable, set false or 0 to disable
|
||||
|
||||
@@ -88,6 +88,22 @@ E.g. <https://rsshub.app/pnas/latest?scihub=1>
|
||||
|
||||
E.g. <https://rsshub.app/dcard/posts/popular?opencc=t2s>
|
||||
|
||||
## Multimedia processing
|
||||
|
||||
::: warning 注意
|
||||
|
||||
This is an experimental API
|
||||
|
||||
The following operation allows user to inject codes, which is harmful in web environment. However, RSS feed reader usually limits these functions. While normally routes won't need these functions, please set `ALLOW_USER_HOTLINK_TEMPLATE` to `true` if you understand how these parameters works.
|
||||
|
||||
:::
|
||||
|
||||
- `image_hotlink_template`: replace image URL in the description to avoid anti-hotlink protection, leave it blank to disable this function. Usage reference [#2769](https://github.com/DIYgod/RSSHub/issues/2769). You may use any property listed in [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL#Properties) (suffixing with `_ue` results in URL encoding), format of JS template literal. e.g. `${protocol}//${host}${pathname}`, `https://i3.wp.com/${host}${pathname}`, `https://images.weserv.nl?url=${href_ue}`
|
||||
- `multimedia_hotlink_template`: the same as `image_hotlink_template` but apply to audio and video. Note: the service must follow redirects, allow reverse-proxy for audio and video, and must drop the `Referer` header when reverse-proxying. [Here is an easy-to-deploy project that fits these requirements](https://github.com/Rongronggg9/rsstt-img-relay). The project accepts simple URL concatenation, e.g. `https://example.com/${href}`, in which `example.com` should be replaced with the domain name of the service you've deployed
|
||||
- `wrap_multimedia_in_iframe`: wrap audio and video in `<iframe>` to prevent the reader from sending `Referer` header. This workaround is only compatible with a few readers, such as RSS Guard and Akregator, which may not support the previous method. You can try this method in such a case
|
||||
|
||||
There are more details in the [FAQ](/en/faq.html).
|
||||
|
||||
## Output Formats
|
||||
|
||||
RSSHub conforms to RSS 2.0 and Atom Standard, simply append `.rss` `.atom` to the end of the feed address to obtain the feed in corresponding format. The default output format is RSS 2.0.
|
||||
@@ -98,3 +114,21 @@ For example:
|
||||
- RSS 2.0 - [https://rsshub.app/dribbble/popular.rss](https://rsshub.app/dribbble/popular.rss)
|
||||
- Atom - [https://rsshub.app/dribbble/popular.atom](https://rsshub.app/dribbble/popular.atom)
|
||||
- Apply filters or URL query [https://rsshub.app/dribbble/popular.atom?filterout=Blue|Yellow|Black](https://rsshub.app/dribbble/popular.atom?filterout=Blue|Yellow|Black)
|
||||
|
||||
### Debug
|
||||
|
||||
If the RSSHub instance is running with `debugInfo=true` enabled, suffixing a route with `.debug.json` will result in the value of `ctx.state.json` being returned.
|
||||
|
||||
This feature aims to facilitate debugging or developing customized features. A route developer has the freedom to determine whether to adopt it or not, without any format requirement.
|
||||
|
||||
For example:
|
||||
|
||||
- `/furstar/characters/cn.debug.json`
|
||||
|
||||
## Brief introduction
|
||||
|
||||
Set the parameter `brief` to generate a brief pure-text introduction with a limited number of characters ( ≥ `100`).
|
||||
|
||||
For example:
|
||||
|
||||
- Brief introduction with 100 characters: `?brief=100`
|
||||
|
||||
@@ -18,12 +18,10 @@
|
||||
|
||||
**Q: 为什么 RSSHub 里的图片 / 视频加载不出来?**
|
||||
|
||||
**A:** RSSHub 里的图片 / 视频地址都是源站地址,部分有防盗链,所以 RSSHub 给图片加了 `referrerpolicy="no-referrer"` 属性来防止跨域问题,但部分 RSS 服务会自作主张去掉这个属性,如 Feedly、Inoreader,在它们的网页端图片会触发跨域加载不出来。部分网站则要求 Referrer 来防止盗链,这种情况可以通过设置图片反代进行处理。详情请查看文档`部署->图片处理`部分
|
||||
**A:** RSSHub 里的图片 / 视频地址都是源站地址,部分有防盗链,所以 RSSHub 给图片加了 `referrerpolicy="no-referrer"` 属性来防止跨域问题,但部分 RSS 服务会自作主张去掉这个属性,如 Feedly、Inoreader,在它们的网页端图片会触发跨域加载不出来。同时,视频目前没有类似的属性,因此大部分阅读器都无法通过防盗链检查。下面是一些解决方案:
|
||||
|
||||
视频目前没有类似的属性,下面是一些解决方案:
|
||||
|
||||
1. 使用不发送 Referer 的阅读器,如 [Inoreader 网页版](https://www.inoreader.com/)配合[禁用 referer 的 user script](https://greasyfork.org/zh-CN/scripts/376884-%E6%98%BE%E7%A4%BA%E9%98%B2%E7%9B%97%E9%93%BE%E5%9B%BE%E7%89%87-for-inoreader)、[RSS to Telegram Bot](https://github.com/Rongronggg9/RSS-to-Telegram-Bot) 等。如果你的阅读器能够在不启用上述两个变通解决方案时成功播放内嵌视频,那么它就是不发送 Referer 的,请考虑添加到文档里帮助更多的人。
|
||||
2. 部分路由支持生成关闭内嵌视频,直接输出视频地址,或者其他可以尝试绕开相关限制的的订阅,可尝试使用。具体说明请查看相关路由(举例:`社交媒体->抖音`)
|
||||
1. 使用不发送 Referer 的阅读器,如 [Inoreader 网页版](https://www.inoreader.com/)配合[禁用 Referer 的 user script](https://greasyfork.org/zh-CN/scripts/376884)、[RSS to Telegram Bot](https://github.com/Rongronggg9/RSS-to-Telegram-Bot) 等。如果你的阅读器能够绕过防盗链成功播放内嵌视频,那么它就是不发送 Referer 的,请考虑添加到文档里帮助更多的人。
|
||||
2. 设置反代,参考 [通用参数 -> 多媒体处理](/parameter.html#duo-mei-ti-chu-li)。
|
||||
3. 回到原网站查看相关资源。
|
||||
|
||||
**Q: 没有我想订阅的网站怎么办嘤嘤嘤 QAQ**
|
||||
|
||||
@@ -529,7 +529,13 @@ RSSHub 支持使用访问密钥 / 码,白名单和黑名单三种方式进行
|
||||
|
||||
### 图片处理
|
||||
|
||||
`HOTLINK_TEMPLATE`: 用于处理描述中图片的 URL,绕过防盗链等限制,留空不生效。用法参考 [#2769](https://github.com/DIYgod/RSSHub/issues/2769)。可以使用 [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL#Properties) 的所有属性,格式为 JS 变量模板。例子:`${protocol}//${host}${pathname}`, `https://i3.wp.com/${host}${pathname}`
|
||||
::: info 新配置方式
|
||||
|
||||
我们正在试验新的,更灵活的配置方式。如果有需要,请转到 [通用参数 -> 多媒体处理](/parameter.html#duo-mei-ti-chu-li) 了解更多。
|
||||
|
||||
:::
|
||||
|
||||
`HOTLINK_TEMPLATE`: 用于处理描述中图片的 URL,绕过防盗链等限制,留空不生效。用法参考 [#2769](https://github.com/DIYgod/RSSHub/issues/2769)。可以使用 [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL#Properties) 的所有属性(加上后缀 `_ue` 则会对其进行 URL 编码),格式为 JS 变量模板。例子:`${protocol}//${host}${pathname}`, `https://i3.wp.com/${host}${pathname}`, `https://images.weserv.nl?url=${href_ue}`
|
||||
|
||||
`HOTLINK_INCLUDE_PATHS`: 限制需要处理的路由,只有匹配成功的路由会被处理,设置多项时用英文逗号 `,` 隔开。若不设置,则所有路由都将被处理
|
||||
|
||||
@@ -545,6 +551,16 @@ RSSHub 支持使用访问密钥 / 码,白名单和黑名单三种方式进行
|
||||
|
||||
:::
|
||||
|
||||
### 功能特性
|
||||
|
||||
::: tip 测试特性
|
||||
|
||||
这个板块控制的是一些新特性的选项,默认他们都是关闭的。如果有需要请阅读对应说明后按需开启
|
||||
|
||||
:::
|
||||
|
||||
`ALLOW_USER_HOTLINK_TEMPLATE`: [通用参数 -> 多媒体处理](/parameter.html#duo-mei-ti-chu-li)特性控制
|
||||
|
||||
### 其他应用配置
|
||||
|
||||
`DISALLOW_ROBOT`: 阻止搜索引擎收录,默认开启,设置 false 或 0 关闭
|
||||
|
||||
@@ -3371,6 +3371,12 @@ column 为 third 时可选的 category:
|
||||
|
||||
## 网易新闻
|
||||
|
||||
::: warning 注意
|
||||
|
||||
若视频因防盗链而无法播放,请参考 [通用参数 -> 多媒体处理](/parameter.html#duo-mei-ti-chu-li) 配置 `multimedia_hotlink_template` **或** `wrap_multimedia_in_iframe`。
|
||||
|
||||
:::
|
||||
|
||||
### 今日关注
|
||||
|
||||
<Route author="nczitzk" example="/netease/today" path="/netease/today/:need_content?" :paramsDesc="['需要获取全文,填写 true/yes 表示需要,默认需要']">
|
||||
|
||||
@@ -89,6 +89,22 @@ Telegram 即时预览模式需要在官网制作页面处理模板,请前往[
|
||||
|
||||
举例: <https://rsshub.app/dcard/posts/popular?opencc=t2s>
|
||||
|
||||
## 多媒体处理
|
||||
|
||||
::: warning 注意
|
||||
|
||||
这是个测试中的 API
|
||||
|
||||
下方操作允许任意用户注入链接模版到最终输出结果,针对于 Web 环境来说这是有害的(XSS)。但是 RSS 阅读器内通常是有限制的环境,通常不会带来副作用,一般路由通常不会需要这些功能。如果需要开启,请将 `ALLOW_USER_HOTLINK_TEMPLATE` 环境变量设置为 `true`
|
||||
|
||||
:::
|
||||
|
||||
- `image_hotlink_template`: 用于处理描述中图片的 URL,绕过防盗链等限制,留空不生效。用法参考 [#2769](https://github.com/DIYgod/RSSHub/issues/2769)。可以使用 [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL#Properties) 的所有属性(加上后缀 `_ue` 则会对其进行 URL 编码),格式为 JS 变量模板。例子:`${protocol}//${host}${pathname}`, `https://i3.wp.com/${host}${pathname}`, `https://images.weserv.nl?url=${href_ue}`
|
||||
- `multimedia_hotlink_template`: 用法同 `image_hotlink_template`,但应用于音频和视频。注意:该服务必须跟随跳转、允许反代音频和视频,且必须在反代时丢弃 `Referer` 请求头。[这里有一个符合要求的易于自行搭建的项目](https://github.com/Rongronggg9/rsstt-img-relay/blob/main/README_zh-CN.md),该项目接受直接拼接 URL,即 `https://example.com/${href}`,其中 `example.com` 应替换为自行搭建的服务的域名
|
||||
- `wrap_multimedia_in_iframe`: 将音频和视频包裹在 `<iframe>` 中,以阻止阅读器发送 `Referer` 请求头。支持该变通解决方案的阅读器较少,且可能造成显示错误。有些阅读器,如 RSS Guard、Akregator,可能不支持前一种方法,则可尝试此方法。设置为`1`生效
|
||||
|
||||
[FAQ](/faq.html) 中有更多信息。
|
||||
|
||||
## 输出格式
|
||||
|
||||
RSSHub 同时支持 RSS 2.0 和 Atom 输出格式,在路由末尾添加 `.rss` 或 `.atom` 即可请求对应输出格式,缺省为 RSS 2.0
|
||||
|
||||
@@ -855,23 +855,17 @@ YouTube 官方亦有提供频道 RSS,形如 <https://www.youtube.com/feeds/vid
|
||||
反爬严格,需要启用 puppeteer。\
|
||||
抖音的视频 CDN 会验证 Referer,意味着许多阅读器都无法直接播放内嵌视频,以下是一些变通解决方案:
|
||||
|
||||
1. 填写 `relay`,开启视频反代 (推荐,适合大部分阅读器)。如该服务接受直接拼接 URL,则可直接填入路径,如 `https://example.com/` ;如该服务仅接受 URL 作为参数传入,则确保该参数置于末尾,如 `https://example.com/?url=` 。注意:该服务必须跟随跳转、允许反代视频,且必须在反代时丢弃 Referer 请求头。[这里有一个符合要求的易于自行搭建的项目](https://github.com/Rongronggg9/rsstt-img-relay),该项目接受直接拼接 URL。
|
||||
2. 启用 iframe 变通解决方案,禁止阅读器发送 Referer。支持该变通解决方案的阅读器较少,且可能造成显示错误。有些阅读器,如 RSS Guard、Akregator,可能不支持前一种方法,则可尝试此方法。
|
||||
3. 使用不发送 Referer 的阅读器,如 [Inoreader 网页版](https://www.inoreader.com/)配合[禁用 referer 的 user script](https://greasyfork.org/zh-CN/scripts/376884-%E6%98%BE%E7%A4%BA%E9%98%B2%E7%9B%97%E9%93%BE%E5%9B%BE%E7%89%87-for-inoreader)、[RSS to Telegram Bot](https://github.com/Rongronggg9/RSS-to-Telegram-Bot) 等。如果你的阅读器能够在不启用上述两个变通解决方案时成功播放内嵌视频,那么它就是不发送 Referer 的,请考虑添加到文档里帮助更多的人。
|
||||
4. 关闭内嵌视频 (`embed=0`),手动点击 `视频直链` 超链接,一般情况下均可成功播放视频。若仍然出现 HTTP 403,请复制 URL 以后到浏览器打开。
|
||||
5. 点击原文链接打开抖音网页版的视频详情页播放视频。
|
||||
|
||||
上述外部链接与 RSSHub 无关。
|
||||
1. 启用内嵌视频 (`embed=1`), 参考 [通用参数 -> 多媒体处理](/parameter.html#duo-mei-ti-chu-li) 配置 `multimedia_hotlink_template` **或** `wrap_multimedia_in_iframe`。
|
||||
2. 关闭内嵌视频 (`embed=0`),手动点击 `视频直链` 超链接,一般情况下均可成功播放视频。若仍然出现 HTTP 403,请复制 URL 以后到浏览器打开。
|
||||
3. 点击原文链接打开抖音网页版的视频详情页播放视频。
|
||||
|
||||
:::
|
||||
|
||||
额外参数
|
||||
|
||||
| 键 | 含义 | 值 | 默认值 |
|
||||
| -------- | ----------------------------------- | ---------------------- | ------- |
|
||||
| `embed` | 是否启用内嵌视频 | `0`/`1`/`true`/`false` | `false` |
|
||||
| `iframe` | 是否启用 iframe 变通解决方案,仅在内嵌视频开启时有效,详见下文 | `0`/`1`/`true`/`false` | `false` |
|
||||
| `relay` | 视频反代服务的 URL,仅在内嵌视频开启时有效,详见下文 | | |
|
||||
| 键 | 含义 | 值 | 默认值 |
|
||||
| ------- | -------- | ---------------------- | ------- |
|
||||
| `embed` | 是否启用内嵌视频 | `0`/`1`/`true`/`false` | `false` |
|
||||
|
||||
### 博主
|
||||
|
||||
|
||||
@@ -91,6 +91,9 @@ const calculateValue = () => {
|
||||
includePaths: envs.HOTLINK_INCLUDE_PATHS && envs.HOTLINK_INCLUDE_PATHS.split(','),
|
||||
excludePaths: envs.HOTLINK_EXCLUDE_PATHS && envs.HOTLINK_EXCLUDE_PATHS.split(','),
|
||||
},
|
||||
feature: {
|
||||
allow_user_hotlink_template: envs.ALLOW_USER_HOTLINK_TEMPLATE === 'true',
|
||||
},
|
||||
suffix: envs.SUFFIX,
|
||||
titleLengthLimit: parseInt(envs.TITLE_LENGTH_LIMIT) || 150,
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
const config = require('@/config').value;
|
||||
const cheerio = require('cheerio');
|
||||
const logger = require('@/utils/logger');
|
||||
const path = require('path');
|
||||
const { art } = require('@/utils/render');
|
||||
|
||||
const templateRegex = /\$\{([^{}]+)}/g;
|
||||
const allowedUrlProperties = ['hash', 'host', 'hostname', 'href', 'origin', 'password', 'pathname', 'port', 'protocol', 'search', 'searchParams', 'username'];
|
||||
const IframeWrapperTemplate = path.join(__dirname, 'templates/iframe.art');
|
||||
|
||||
// match path or sub-path
|
||||
const matchPath = (path, paths) => {
|
||||
@@ -19,9 +25,16 @@ const filterPath = (path) => {
|
||||
return !(include && !matchPath(path, include)) && !(exclude && matchPath(path, exclude));
|
||||
};
|
||||
|
||||
const interpolate = (str, obj) => str.replace(/\${([^}]+)}/g, (_, prop) => obj[prop]);
|
||||
// I don't want to keep another regex and
|
||||
// URL will be the standard way to parse URL
|
||||
const interpolate = (str, obj) =>
|
||||
str.replace(templateRegex, (_, prop) => {
|
||||
let needEncode = false;
|
||||
if (prop.endsWith('_ue')) {
|
||||
// url encode
|
||||
prop = prop.slice(0, -3);
|
||||
needEncode = true;
|
||||
}
|
||||
return needEncode ? encodeURIComponent(obj[prop]) : obj[prop];
|
||||
});
|
||||
const parseUrl = (str) => {
|
||||
let url;
|
||||
try {
|
||||
@@ -32,48 +45,101 @@ const parseUrl = (str) => {
|
||||
|
||||
return url;
|
||||
};
|
||||
const replaceUrls = (body, template) => {
|
||||
// const $ = cheerio.load(body, { decodeEntities: false, xmlMode: true });
|
||||
// `<br><img><hr><video><source>abc</video>` => `<br><img><hr><video><source>abc</source></video></hr></img></br>`
|
||||
// so awful...
|
||||
// "In HTML, using a closing tag on an empty element is usually invalid."
|
||||
// https://developer.mozilla.org/en-US/docs/Glossary/Empty_element
|
||||
// I guess it is just a workaround to drop `<html><head></head><body>`, so this is what we exactly need:
|
||||
const $ = cheerio.load(body, null, false);
|
||||
$('img').each(function () {
|
||||
const old_src = $(this).attr('src');
|
||||
const url = parseUrl(old_src);
|
||||
if (url) {
|
||||
const new_src = interpolate(template, url);
|
||||
$(this).attr('src', new_src);
|
||||
const replaceUrls = ($, selector, template, attribute = 'src') => {
|
||||
$(selector).each(function () {
|
||||
const old_src = $(this).attr(attribute);
|
||||
if (old_src) {
|
||||
const url = parseUrl(old_src);
|
||||
if (url) {
|
||||
// We are now accepting user input and giving faith that reader will do the
|
||||
// right thing, default this config would be turn off.
|
||||
// if admins know the risk of their site might be used for hosting for hijacking
|
||||
// content, e.g. they could set corresponded config to true to turn that feature on
|
||||
$(this).attr(attribute, interpolate(template, url));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return $.root().html();
|
||||
const wrapWithIframe = ($, selector) => {
|
||||
$(selector).each((_, elem) => {
|
||||
elem = $(elem);
|
||||
elem.replaceWith(art(IframeWrapperTemplate, { content: elem.toString() }));
|
||||
});
|
||||
};
|
||||
|
||||
const process = (html, image_hotlink_template, multimedia_hotlink_template, wrap_multimedia_in_iframe) => {
|
||||
const $ = cheerio.load(html, undefined, false);
|
||||
if (image_hotlink_template) {
|
||||
replaceUrls($, 'img, picture > source', image_hotlink_template);
|
||||
replaceUrls($, 'video[poster]', image_hotlink_template, 'poster');
|
||||
}
|
||||
if (multimedia_hotlink_template) {
|
||||
replaceUrls($, 'video, video > source, audio, audio > source', multimedia_hotlink_template);
|
||||
if (!image_hotlink_template) {
|
||||
replaceUrls($, 'video[poster]', multimedia_hotlink_template, 'poster');
|
||||
}
|
||||
}
|
||||
if (wrap_multimedia_in_iframe) {
|
||||
wrapWithIframe($, 'video, audio');
|
||||
}
|
||||
return $.html();
|
||||
};
|
||||
|
||||
const validateTemplate = (template) => {
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
[...template.matchAll(templateRegex)].forEach((match) => {
|
||||
const prop = match[1].endsWith('_ue') ? match[1].slice(0, -3) : match[1];
|
||||
if (!allowedUrlProperties.includes(prop)) {
|
||||
throw new Error(`Invalid URL property: ${prop}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = async (ctx, next) => {
|
||||
await next();
|
||||
|
||||
const template = config.hotlink.template;
|
||||
let image_hotlink_template = undefined;
|
||||
let multimedia_hotlink_template = undefined;
|
||||
const shouldWrapInIframe = ctx.query.wrap_multimedia_in_iframe === '1';
|
||||
|
||||
if (!template || !filterPath(ctx.request.path)) {
|
||||
// Read params if enabled
|
||||
if (config.feature.allow_user_hotlink_template) {
|
||||
multimedia_hotlink_template = ctx.query.multimedia_hotlink_template;
|
||||
image_hotlink_template = ctx.query.image_hotlink_template;
|
||||
}
|
||||
|
||||
// Force config hotlink template on conflict
|
||||
if (config.hotlink.template) {
|
||||
if (!filterPath(ctx.request.path)) {
|
||||
image_hotlink_template = undefined;
|
||||
} else {
|
||||
image_hotlink_template = config.hotlink.template;
|
||||
}
|
||||
}
|
||||
|
||||
if (!image_hotlink_template && !multimedia_hotlink_template && !shouldWrapInIframe) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateTemplate(image_hotlink_template);
|
||||
validateTemplate(multimedia_hotlink_template);
|
||||
|
||||
// Assume that only description include image link
|
||||
// and here we will only check them in description.
|
||||
// Use Cheerio to load the description as html and filter all
|
||||
// image link
|
||||
if (ctx.state.data) {
|
||||
if (ctx.state.data.description) {
|
||||
ctx.state.data.description = replaceUrls(ctx.state.data.description, template);
|
||||
ctx.state.data.description = process(ctx.state.data.description, image_hotlink_template, multimedia_hotlink_template, shouldWrapInIframe);
|
||||
}
|
||||
|
||||
ctx.state.data.item &&
|
||||
ctx.state.data.item.forEach((item) => {
|
||||
if (item.description) {
|
||||
item.description = replaceUrls(item.description, template);
|
||||
item.description = process(item.description, image_hotlink_template, multimedia_hotlink_template, shouldWrapInIframe);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ const resolveRelativeLink = ($, elem, attr, baseUrl) => {
|
||||
|
||||
if (baseUrl) {
|
||||
try {
|
||||
$elem.attr(attr, new URL($elem.attr(attr), baseUrl).href);
|
||||
const oldAttr = $elem.attr(attr);
|
||||
if (oldAttr) {
|
||||
// e.g. <video><source src="https://example.com"></video> should leave <video> unchanged
|
||||
$elem.attr(attr, new URL(oldAttr, baseUrl).href);
|
||||
}
|
||||
} catch (e) {
|
||||
// no-empty
|
||||
}
|
||||
@@ -124,6 +128,9 @@ module.exports = async (ctx, next) => {
|
||||
$('img, video, audio, source, iframe, embed, track').each((_, elem) => {
|
||||
resolveRelativeLink($, elem, 'src', baseUrl);
|
||||
});
|
||||
$('video[poster]').each((_, elem) => {
|
||||
resolveRelativeLink($, elem, 'poster', baseUrl);
|
||||
});
|
||||
$('img, iframe').each((_, elem) => {
|
||||
$(elem).attr('referrerpolicy', 'no-referrer');
|
||||
});
|
||||
@@ -280,6 +287,8 @@ module.exports = async (ctx, next) => {
|
||||
throw Error(`Invalid parameter <code>brief=${ctx.query.brief}</code>. Please check the doc https://docs.rsshub.app/parameter.html#shu-chu-jian-xun`);
|
||||
}
|
||||
}
|
||||
|
||||
// some parameters are processed in `anti-hotlink.js`
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
14
lib/middleware/templates/iframe.art
Normal file
14
lib/middleware/templates/iframe.art
Normal file
@@ -0,0 +1,14 @@
|
||||
<iframe referrerpolicy="no-referrer" width=100% height=150vh frameborder=0 marginheight=0 marginwidth=0
|
||||
style="border:0; margin:0; padding:0; width:100%; height:150vh;"
|
||||
srcdoc="
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="referrer" content="no-referrer">
|
||||
</head>
|
||||
<body>
|
||||
{{ content }}
|
||||
</body>
|
||||
</html>
|
||||
">
|
||||
</iframe>
|
||||
@@ -103,7 +103,7 @@ module.exports = async (ctx) => {
|
||||
title: `Multimedia Title`,
|
||||
description: `<img src="/DIYgod/RSSHub.jpg">
|
||||
<video src="/DIYgod/RSSHub.mp4"></video>
|
||||
<video>
|
||||
<video poster="/DIYgod/RSSHub.jpg">
|
||||
<source src="/DIYgod/RSSHub.mp4" type="video/mp4">
|
||||
<source src="/DIYgod/RSSHub.webm" type="video/webm">
|
||||
</video>
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
const supertest = require('supertest');
|
||||
jest.mock('request-promise-native');
|
||||
const Parser = require('rss-parser');
|
||||
const querystring = require('query-string');
|
||||
const parser = new Parser();
|
||||
jest.setTimeout(50000);
|
||||
let server;
|
||||
|
||||
afterAll(() => {
|
||||
delete process.env.HOTLINK_TEMPLATE;
|
||||
delete process.env.HOTLINK_INCLUDE_PATHS;
|
||||
delete process.env.HOTLINK_EXCLUDE_PATHS;
|
||||
delete process.env.ALLOW_USER_HOTLINK_TEMPLATE;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.HOTLINK_TEMPLATE;
|
||||
delete process.env.HOTLINK_INCLUDE_PATHS;
|
||||
delete process.env.HOTLINK_EXCLUDE_PATHS;
|
||||
jest.resetModules();
|
||||
delete process.env.ALLOW_USER_HOTLINK_TEMPLATE;
|
||||
server.close();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
const origin1 = `<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
const expects = {
|
||||
complicated: {
|
||||
origin: {
|
||||
items: [
|
||||
`<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">
|
||||
|
||||
<a href="http://mock.com/DIYgod/RSSHub"></a>
|
||||
@@ -27,9 +35,15 @@ const origin1 = `<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img data-mock="/DIYgod/RSSHub.png" src="https://mock.com/DIYgod/RSSHub.png" referrerpolicy="no-referrer">
|
||||
<img mock="/DIYgod/RSSHub.gif" src="https://mock.com/DIYgod/RSSHub.gif" referrerpolicy="no-referrer">
|
||||
<img src="http://mock.com/DIYgod/DIYgod/RSSHub" referrerpolicy="no-referrer">
|
||||
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`;
|
||||
|
||||
const processed1 = `<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`,
|
||||
`<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`,
|
||||
],
|
||||
desc: '<img src="http://mock.com/DIYgod/DIYgod/RSSHub"> - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)',
|
||||
},
|
||||
processed: {
|
||||
items: [
|
||||
`<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img src="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">
|
||||
|
||||
<a href="http://mock.com/DIYgod/RSSHub"></a>
|
||||
@@ -38,104 +52,268 @@ const processed1 = `<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img data-mock="/DIYgod/RSSHub.png" src="https://i3.wp.com/mock.com/DIYgod/RSSHub.png" referrerpolicy="no-referrer">
|
||||
<img mock="/DIYgod/RSSHub.gif" src="https://i3.wp.com/mock.com/DIYgod/RSSHub.gif" referrerpolicy="no-referrer">
|
||||
<img src="https://i3.wp.com/mock.com/DIYgod/DIYgod/RSSHub" referrerpolicy="no-referrer">
|
||||
<img src="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`;
|
||||
<img src="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`,
|
||||
`<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img src="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`,
|
||||
],
|
||||
desc: '<img src="https://i3.wp.com/mock.com/DIYgod/DIYgod/RSSHub"> - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)',
|
||||
},
|
||||
urlencoded: {
|
||||
items: [
|
||||
`<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.jpg" referrerpolicy="no-referrer">
|
||||
|
||||
const origin2 = `<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`;
|
||||
<a href="http://mock.com/DIYgod/RSSHub"></a>
|
||||
<img src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.jpg" data-src="/DIYgod/RSSHub0.jpg" referrerpolicy="no-referrer">
|
||||
<img data-src="/DIYgod/RSSHub.jpg" src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.jpg" referrerpolicy="no-referrer">
|
||||
<img data-mock="/DIYgod/RSSHub.png" src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.png" referrerpolicy="no-referrer">
|
||||
<img mock="/DIYgod/RSSHub.gif" src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.gif" referrerpolicy="no-referrer">
|
||||
<img src="https://images.weserv.nl?url=http%3A%2F%2Fmock.com%2FDIYgod%2FDIYgod%2FRSSHub" referrerpolicy="no-referrer">
|
||||
<img src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.jpg" referrerpolicy="no-referrer">`,
|
||||
`<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img src="https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.jpg" referrerpolicy="no-referrer">`,
|
||||
],
|
||||
desc: '<img src="https://images.weserv.nl?url=http%3A%2F%2Fmock.com%2FDIYgod%2FDIYgod%2FRSSHub"> - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)',
|
||||
},
|
||||
},
|
||||
multimedia: {
|
||||
origin: {
|
||||
items: [
|
||||
`<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">
|
||||
<video src="https://mock.com/DIYgod/RSSHub.mp4"></video>
|
||||
<video poster="https://mock.com/DIYgod/RSSHub.jpg">
|
||||
<source src="https://mock.com/DIYgod/RSSHub.mp4" type="video/mp4">
|
||||
<source src="https://mock.com/DIYgod/RSSHub.webm" type="video/webm">
|
||||
</video>
|
||||
<audio src="https://mock.com/DIYgod/RSSHub.mp3"></audio>
|
||||
<iframe src="https://mock.com/DIYgod/RSSHub.html" referrerpolicy="no-referrer"></iframe>`,
|
||||
],
|
||||
desc: '<video src="http://mock.com/DIYgod/DIYgod/RSSHub"></video> - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)',
|
||||
},
|
||||
relayed: {
|
||||
items: [
|
||||
`<img src="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">
|
||||
<video src="https://i3.wp.com/mock.com/DIYgod/RSSHub.mp4"></video>
|
||||
<video poster="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg">
|
||||
<source src="https://i3.wp.com/mock.com/DIYgod/RSSHub.mp4" type="video/mp4">
|
||||
<source src="https://i3.wp.com/mock.com/DIYgod/RSSHub.webm" type="video/webm">
|
||||
</video>
|
||||
<audio src="https://i3.wp.com/mock.com/DIYgod/RSSHub.mp3"></audio>
|
||||
<iframe src="https://mock.com/DIYgod/RSSHub.html" referrerpolicy="no-referrer"></iframe>`,
|
||||
],
|
||||
desc: '<video src="https://i3.wp.com/mock.com/DIYgod/DIYgod/RSSHub"></video> - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)',
|
||||
},
|
||||
partlyRelayed: {
|
||||
items: [
|
||||
`<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">
|
||||
<video src="https://i3.wp.com/mock.com/DIYgod/RSSHub.mp4"></video>
|
||||
<video poster="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg">
|
||||
<source src="https://i3.wp.com/mock.com/DIYgod/RSSHub.mp4" type="video/mp4">
|
||||
<source src="https://i3.wp.com/mock.com/DIYgod/RSSHub.webm" type="video/webm">
|
||||
</video>
|
||||
<audio src="https://i3.wp.com/mock.com/DIYgod/RSSHub.mp3"></audio>
|
||||
<iframe src="https://mock.com/DIYgod/RSSHub.html" referrerpolicy="no-referrer"></iframe>`,
|
||||
],
|
||||
desc: '<video src="https://i3.wp.com/mock.com/DIYgod/DIYgod/RSSHub"></video> - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)',
|
||||
},
|
||||
wrappedInIframe: {
|
||||
items: [
|
||||
`<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">
|
||||
<iframe referrerpolicy="no-referrer" width="100%" height="150vh" frameborder="0" marginheight="0" marginwidth="0" style="border:0; margin:0; padding:0; width:100%; height:150vh;" srcdoc="
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="referrer" content="no-referrer">
|
||||
</head>
|
||||
<body>
|
||||
<video src="https://mock.com/DIYgod/RSSHub.mp4"></video>
|
||||
</body>
|
||||
</html>
|
||||
">
|
||||
</iframe>
|
||||
|
||||
const processed2 = `<a href="https://mock.com/DIYgod/RSSHub"></a>
|
||||
<img src="https://i3.wp.com/mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">`;
|
||||
<iframe referrerpolicy="no-referrer" width="100%" height="150vh" frameborder="0" marginheight="0" marginwidth="0" style="border:0; margin:0; padding:0; width:100%; height:150vh;" srcdoc="
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="referrer" content="no-referrer">
|
||||
</head>
|
||||
<body>
|
||||
<video poster="https://mock.com/DIYgod/RSSHub.jpg">
|
||||
<source src="https://mock.com/DIYgod/RSSHub.mp4" type="video/mp4">
|
||||
<source src="https://mock.com/DIYgod/RSSHub.webm" type="video/webm">
|
||||
</video>
|
||||
</body>
|
||||
</html>
|
||||
">
|
||||
</iframe>
|
||||
|
||||
const oriRouteDesc = '<img src="http://mock.com/DIYgod/DIYgod/RSSHub"> - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)';
|
||||
<iframe referrerpolicy="no-referrer" width="100%" height="150vh" frameborder="0" marginheight="0" marginwidth="0" style="border:0; margin:0; padding:0; width:100%; height:150vh;" srcdoc="
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="referrer" content="no-referrer">
|
||||
</head>
|
||||
<body>
|
||||
<audio src="https://mock.com/DIYgod/RSSHub.mp3"></audio>
|
||||
</body>
|
||||
</html>
|
||||
">
|
||||
</iframe>
|
||||
|
||||
const proRouteDesc = '<img src="https://i3.wp.com/mock.com/DIYgod/DIYgod/RSSHub"> - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)';
|
||||
<iframe src="https://mock.com/DIYgod/RSSHub.html" referrerpolicy="no-referrer"></iframe>`,
|
||||
],
|
||||
desc: `<iframe referrerpolicy="no-referrer" width="100%" height="150vh" frameborder="0" marginheight="0" marginwidth="0" style="border:0; margin:0; padding:0; width:100%; height:150vh;" srcdoc="
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="referrer" content="no-referrer">
|
||||
</head>
|
||||
<body>
|
||||
<video src="http://mock.com/DIYgod/DIYgod/RSSHub"></video>
|
||||
</body>
|
||||
</html>
|
||||
">
|
||||
</iframe>
|
||||
- Made with love by RSSHub(https://github.com/DIYgod/RSSHub)`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const testAntiHotlink = async (expect1, expect2, expectRouteDesc) => {
|
||||
const testAntiHotlink = async (path, expectObj, query) => {
|
||||
server = require('../../lib/index');
|
||||
const request = supertest(server);
|
||||
|
||||
const response = await request.get('/test/complicated');
|
||||
let queryStr;
|
||||
if (query) {
|
||||
queryStr = typeof query === 'string' ? query : querystring.stringify(query);
|
||||
}
|
||||
path = path + (queryStr ? `?${queryStr}` : '');
|
||||
|
||||
const response = await request.get(path);
|
||||
const parsed = await parser.parseString(response.text);
|
||||
expect(parsed.items[0].content).toBe(expect1);
|
||||
expect(parsed.items[1].content).toBe(expect2);
|
||||
expect(parsed.description).toBe(expectRouteDesc);
|
||||
expect({
|
||||
items: parsed.items.slice(0, expectObj.items.length).map((i) => i.content),
|
||||
desc: parsed.description,
|
||||
}).toStrictEqual(expectObj);
|
||||
|
||||
return parsed;
|
||||
};
|
||||
|
||||
const expectOrigin = async () => await testAntiHotlink(origin1, origin2, oriRouteDesc);
|
||||
|
||||
const expectProcessed = async () => await testAntiHotlink(processed1, processed2, proRouteDesc);
|
||||
const expectImgOrigin = async (query) => await testAntiHotlink('/test/complicated', expects.complicated.origin, query);
|
||||
const expectImgProcessed = async (query) => await testAntiHotlink('/test/complicated', expects.complicated.processed, query);
|
||||
const expectImgUrlencoded = async (query) => await testAntiHotlink('/test/complicated', expects.complicated.urlencoded, query);
|
||||
const expectMultimediaOrigin = async (query) => await testAntiHotlink('/test/multimedia', expects.multimedia.origin, query);
|
||||
const expectMultimediaRelayed = async (query) => await testAntiHotlink('/test/multimedia', expects.multimedia.relayed, query);
|
||||
const expectMultimediaPartlyRelayed = async (query) => await testAntiHotlink('/test/multimedia', expects.multimedia.partlyRelayed, query);
|
||||
const expectMultimediaWrappedInIframe = async (query) => await testAntiHotlink('/test/multimedia', expects.multimedia.wrappedInIframe, query);
|
||||
|
||||
describe('anti-hotlink', () => {
|
||||
// First-time require is really, really slow.
|
||||
// If someone merely runs this test unit instead of the whole suite and this stage does not exist,
|
||||
// the next one will sometimes time out, so we need to firstly require it once.
|
||||
it('server-require', () => {
|
||||
server = require('../../lib/index');
|
||||
it('template-legacy', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}';
|
||||
await expectImgProcessed();
|
||||
});
|
||||
|
||||
it('template', async () => {
|
||||
it('template-experimental', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}';
|
||||
await expectProcessed();
|
||||
process.env.ALLOW_USER_HOTLINK_TEMPLATE = 'true';
|
||||
await expectImgProcessed();
|
||||
await expectMultimediaRelayed({ multimedia_hotlink_template: process.env.HOTLINK_TEMPLATE });
|
||||
});
|
||||
|
||||
it('url', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = '${protocol}//${host}${pathname}';
|
||||
await expectOrigin();
|
||||
await expectImgOrigin();
|
||||
await expectMultimediaOrigin({ multimedia_hotlink_template: process.env.HOTLINK_TEMPLATE });
|
||||
});
|
||||
|
||||
it('url-encoded', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://images.weserv.nl?url=${href_ue}';
|
||||
await expectImgUrlencoded();
|
||||
});
|
||||
|
||||
it('template-priority-legacy', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = '${protocol}//${host}${pathname}';
|
||||
await expectImgOrigin();
|
||||
});
|
||||
|
||||
it('template-priority-experimental', async () => {
|
||||
process.env.ALLOW_USER_HOTLINK_TEMPLATE = 'true';
|
||||
await expectImgOrigin();
|
||||
await expectImgProcessed({ image_hotlink_template: 'https://i3.wp.com/${host}${pathname}' });
|
||||
});
|
||||
|
||||
it('no-template', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = '';
|
||||
await expectOrigin();
|
||||
await expectImgOrigin();
|
||||
await expectMultimediaOrigin();
|
||||
});
|
||||
|
||||
it('multimedia-template-experimental', async () => {
|
||||
process.env.ALLOW_USER_HOTLINK_TEMPLATE = 'true';
|
||||
await expectMultimediaOrigin({ multimedia_hotlink_template: '${protocol}//${host}${pathname}' });
|
||||
await expectMultimediaPartlyRelayed({ multimedia_hotlink_template: 'https://i3.wp.com/${host}${pathname}' });
|
||||
});
|
||||
|
||||
it('multimedia-wrapped-in-iframe-experimental', async () => {
|
||||
await expectMultimediaWrappedInIframe({ wrap_multimedia_in_iframe: '1' });
|
||||
});
|
||||
|
||||
it('include-paths-partial-matched', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}';
|
||||
process.env.HOTLINK_INCLUDE_PATHS = '/test';
|
||||
await expectProcessed();
|
||||
await expectImgProcessed();
|
||||
});
|
||||
|
||||
it('include-paths-fully-matched', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}';
|
||||
process.env.HOTLINK_INCLUDE_PATHS = '/test/complicated';
|
||||
await expectProcessed();
|
||||
await expectImgProcessed();
|
||||
});
|
||||
|
||||
it('include-paths-unmatched', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}';
|
||||
process.env.HOTLINK_INCLUDE_PATHS = '/t';
|
||||
await expectOrigin();
|
||||
await expectImgOrigin();
|
||||
});
|
||||
|
||||
it('exclude-paths-partial-matched', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}';
|
||||
process.env.HOTLINK_EXCLUDE_PATHS = '/test';
|
||||
await expectOrigin();
|
||||
await expectImgOrigin();
|
||||
});
|
||||
|
||||
it('exclude-paths-fully-matched', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}';
|
||||
process.env.HOTLINK_EXCLUDE_PATHS = '/test/complicated';
|
||||
await expectOrigin();
|
||||
await expectImgOrigin();
|
||||
});
|
||||
|
||||
it('exclude-paths-unmatched', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}';
|
||||
process.env.HOTLINK_EXCLUDE_PATHS = '/t';
|
||||
await expectProcessed();
|
||||
await expectImgProcessed();
|
||||
});
|
||||
|
||||
it('include-exclude-paths-mixed-filtered-out', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}';
|
||||
process.env.HOTLINK_INCLUDE_PATHS = '/test';
|
||||
process.env.HOTLINK_EXCLUDE_PATHS = '/test/complicated';
|
||||
await expectOrigin();
|
||||
await expectImgOrigin();
|
||||
});
|
||||
|
||||
it('include-exclude-paths-mixed-unfiltered-out', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}';
|
||||
process.env.HOTLINK_INCLUDE_PATHS = '/test';
|
||||
process.env.HOTLINK_EXCLUDE_PATHS = '/test/c';
|
||||
await expectProcessed();
|
||||
await expectImgProcessed();
|
||||
});
|
||||
|
||||
it('invalid-property', async () => {
|
||||
process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${createObjectURL}';
|
||||
server = require('../../lib/index');
|
||||
const request = supertest(server);
|
||||
const response = await request.get('/test/complicated');
|
||||
expect(response.text).toContain('Error: Invalid URL property: createObjectURL');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -287,7 +287,7 @@ describe('multimedia_description', () => {
|
||||
const parsed = await parser.parseString(response.text);
|
||||
expect(parsed.items[0].content).toBe(`<img src="https://mock.com/DIYgod/RSSHub.jpg" referrerpolicy="no-referrer">
|
||||
<video src="https://mock.com/DIYgod/RSSHub.mp4"></video>
|
||||
<video src="https://mock.com/DIYgod/undefined">
|
||||
<video poster="https://mock.com/DIYgod/RSSHub.jpg">
|
||||
<source src="https://mock.com/DIYgod/RSSHub.mp4" type="video/mp4">
|
||||
<source src="https://mock.com/DIYgod/RSSHub.webm" type="video/webm">
|
||||
</video>
|
||||
|
||||
Reference in New Issue
Block a user