tests/test_follow_stream.py
Rama master · réplica de lectura del repositorio original.
import threading
import json
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest import mock
import httpx
from peach import http as peach_http
from peach.follow_stream import (
MAX_PROXY_REDIRECTS, FollowMediaResolver, FollowMediaUnavailable,
FollowProxyError, ResolvedFollowMedia, open_upstream, proxy_request_headers,
proxyable,
)
from peach.follow_secrets import Credential
from peach.http import HttpResponse
class FollowMediaResolverTests(unittest.TestCase):
def test_source_cooldown_survives_resolver_restart_and_expires(self):
moment, wall = [1000.0], [11000.0]
transport = mock.Mock()
with tempfile.TemporaryDirectory() as temporary:
options = {'state_root': Path(temporary), 'clock': lambda: moment[0],
'wall_clock': lambda: wall[0]}
first = FollowMediaResolver(transport, **options)
first.pause('iwara', 120)
wall[0] += 30
moment[0] = 5
second = FollowMediaResolver(transport, **options)
self.assertEqual(second.cooling('iwara'), 90)
moment[0] += 91
wall[0] += 91
self.assertEqual(FollowMediaResolver(transport, **options).cooling('iwara'), 0)
transport.assert_not_called()
def test_iwara_transport_cleanup_preserves_the_callers_transport(self):
owned = mock.Mock()
upstream = peach_http.HttpxTransport()
with mock.patch("peach.follow_stream.IwaraConnector", return_value=SimpleNamespace(transport=owned)):
resolver = FollowMediaResolver(upstream)
resolver.close()
owned.close.assert_called_once_with()
upstream.close()
injected = mock.Mock()
FollowMediaResolver(injected).close()
injected.close.assert_not_called()
def test_iwara_playback_uses_shared_cache_quality_selection_and_no_media_credentials(self):
requests = []
def transport(request, timeout, max_bytes):
requests.append(request)
payload = ({"fileUrl": "https://files.iwara.tv/file/id?expires=2000000000",
"file": {"height": 1080}}
if "/video/" in request.url else [
{"name": "Source", "src": {"view": "//hime.iwara.tv/1080.mp4"}},
{"name": "540", "src": {"view": "//mikoto.iwara.tv/540.mp4"}}])
return HttpResponse(200, {}, json.dumps(payload).encode())
item = SimpleNamespace(id=90, provider="iwara", external_id="Video123",
url="https://www.iwara.tv/video/Video123", metadata={},
media_url="https://www.iwara.tv/video/Video123")
resolver = FollowMediaResolver(transport).with_credential_loader(
lambda _: Credential("iwara", {"access_token": "private-token"}))
self.assertEqual(resolver.resolve(item).url, "https://hime.iwara.tv/1080.mp4")
lower = resolver.resolve(item, height=540)
self.assertEqual(lower.url, "https://mikoto.iwara.tv/540.mp4")
self.assertEqual(len(requests), 2)
self.assertEqual(lower.allowed_hosts, ("iwara.tv",))
self.assertIsNone(lower.headers)
self.assertNotIn("Authorization", requests[1].headers)
self.assertTrue(proxyable("iwara", item.media_url))
def test_iwara_access_failure_is_cached_and_rate_limit_pauses_the_source(self):
transport = mock.Mock(return_value=HttpResponse(429, {"Retry-After": "30"}, b""))
resolver = FollowMediaResolver(transport, clock=lambda: 100)
item = SimpleNamespace(id=90, provider="iwara", external_id="Video123",
url="https://www.iwara.tv/video/Video123", metadata={})
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(item)
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(item)
self.assertEqual(transport.call_count, 1)
self.assertEqual(resolver.cooling("iwara"), 30)
def test_iwara_unknown_original_height_preserves_default_and_explicit_selection(self):
transport = mock.Mock(side_effect=[
HttpResponse(200, {}, json.dumps({
"fileUrl": "https://files.iwara.tv/file/id?expires=2000000000",
"file": {"height": 0}}).encode()),
HttpResponse(200, {}, json.dumps([
{"name": "540", "src": {"view": "//hime.iwara.tv/540.mp4"}},
{"name": "Source", "src": {"view": "//mikoto.iwara.tv/original.mp4"}}]).encode())])
resolver = FollowMediaResolver(transport)
item = SimpleNamespace(id=91, provider="iwara", external_id="Video123",
url="https://www.iwara.tv/video/Video123", metadata={})
original = resolver.resolve(item)
self.assertEqual(original.url, "https://mikoto.iwara.tv/original.mp4")
self.assertEqual(original.qualities[0][0], 0)
self.assertEqual(resolver.resolve(item, height=540).url, "https://hime.iwara.tv/540.mp4")
self.assertEqual(resolver.resolve(item, height=0), original)
self.assertEqual(resolver.resolve(item, height=9999), original)
self.assertEqual(transport.call_count, 2)
def test_rule34video_resolves_the_current_signed_url_and_caches_it(self):
requests = []
def transport(request, timeout, max_bytes):
requests.append(request)
return HttpResponse(200, {}, b"<script>video_url: 'https:\\/\\/rule34video.com\\/get_file\\/1.mp4\\/?v-acctoken=fresh'</script>")
item = SimpleNamespace(
id=7, provider="rule34video",
url="https://rule34video.com/video/7/work/",
media_url="https://rule34video.com/get_file/1.mp4/",
metadata={},
)
resolver = FollowMediaResolver(transport)
first = resolver.resolve(item)
second = resolver.resolve(item)
self.assertEqual(first, second)
self.assertEqual(len(requests), 1)
self.assertIn("v-acctoken=fresh", first.url)
self.assertEqual(first.referer, item.url)
def test_non_rule34_media_uses_the_recorded_source_without_a_network_probe(self):
resolver = FollowMediaResolver(lambda *_args: self.fail("unexpected network probe"))
item = SimpleNamespace(
id=8, provider="kemono", url="https://kemono.cr/post/8",
media_url="https://img.kemono.cr/data/8.mp4", metadata={},
)
self.assertEqual(resolver.resolve(item).url, item.media_url)
def test_simpcity_images_on_any_public_image_host_are_proxied_without_a_cookie(self):
"""帖子里的图挂在几十家图站上,白名单追不上;这类媒体不带凭据,放行任意公网主机。"""
resolver = FollowMediaResolver(lambda *_args: self.fail("unexpected network probe"))
item = SimpleNamespace(
id=11, provider="simpcity", url="https://simpcity.cr/threads/17401/post-1",
media_url="https://jpg5.su/img/abc.jpg", metadata={},
)
resolved = resolver.resolve(item)
self.assertEqual(resolved.url, item.media_url)
self.assertIsNone(resolved.headers)
self.assertEqual(resolved.allowed_hosts, ())
self.assertTrue(resolved.public_hosts)
for url in ("https://simp6.cuckcapital.cr/images4/b73b.png", "https://jpg5.su/img/abc.jpg",
"https://i.ibb.co/x/y.png", "https://images2.imgbox.com/ab/cd/ef.jpg"):
self.assertTrue(proxyable("simpcity", url), url)
# 放宽的是域名清单,不是安全边界:明文、IP 字面量、本机与局域网名字、带用户
# 信息的地址照旧拒收。
for url in ("http://jpg5.su/img/abc.jpg", "https://169.254.1.1/a.png", "https://[::1]/a.png",
"https://localhost/a.png", "https://peach.local/a.png", "https://router.lan/a.png",
"https://intranet/a.png", "https://user:pw@jpg5.su/a.png"):
self.assertFalse(proxyable("simpcity", url), url)
# 别的来源仍只认自己的白名单。
self.assertFalse(proxyable("kemono", "https://jpg5.su/img/abc.jpg"))
def test_public_host_media_never_carries_credentials(self):
"""公网模式成立的前提是这条媒体不带凭据,结构上就不允许两者同时出现。"""
with self.assertRaises(ValueError):
ResolvedFollowMedia("https://jpg5.su/a.jpg", headers={"Cookie": "x"},
public_hosts=True)
def test_untrusted_or_credentialed_targets_are_rejected(self):
resolver = FollowMediaResolver(lambda *_args: self.fail("unexpected network probe"))
bad = SimpleNamespace(
id=9, provider="kemono", url="https://kemono.cr/post/9",
media_url="https://127.0.0.1/private.mp4", metadata={},
)
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(bad)
locked = SimpleNamespace(
id=10, provider="f95zone", url="https://f95zone.to/post/10",
media_url="https://f95zone.to/file.mp4",
metadata={"media_needs_credential": True},
)
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(locked)
def test_fanbox_gallery_selects_an_image_by_index(self):
item = SimpleNamespace(
id=11, provider="fanbox", url="https://creator.fanbox.cc/posts/11",
media_url=None, metadata={"media_items": [
{"url": "https://downloads.fanbox.cc/one.jpg",
"resource_provider": "fanbox"},
{"url": "https://downloads.fanbox.cc/two.jpg",
"resource_provider": "fanbox"},
]},
)
target = FollowMediaResolver(lambda *_args: None).resolve(item, 1)
self.assertEqual(target.url, "https://downloads.fanbox.cc/two.jpg")
def test_archive_attachment_list_resolves_only_on_the_items_own_site(self):
"""归档站帖子的附件清单按条目自己站点的主机白名单取,别的主机、别的来源一律拒收。"""
item = SimpleNamespace(
id=15, provider="pawchive", url="https://pawchive.pw/patreon/user/1/post/15",
media_url="https://file.pawchive.pw/data/a/clip.mp4", metadata={"media_items": [
{"url": "https://file.pawchive.pw/data/a/clip.mp4", "resource_provider": "pawchive"},
{"url": "https://file.pawchive.pw/data/a/page2.jpg", "resource_provider": "pawchive"},
{"url": "https://127.0.0.1/data/a/private.jpg", "resource_provider": "pawchive"},
{"url": "https://kemono.cr/data/a/page3.jpg", "resource_provider": "kemono"},
]},
)
resolver = FollowMediaResolver(lambda *_args: None)
target = resolver.resolve(item, 1)
self.assertEqual(target.url, "https://file.pawchive.pw/data/a/page2.jpg")
self.assertEqual(target.referer, item.url)
for index in (2, 3):
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(item, index)
def test_legacy_f95_attachment_is_resolved_as_an_image(self):
item = SimpleNamespace(
id=13, provider="f95zone", url="https://f95zone.to/threads/1/post-13",
media_url="https://pixeldrain.com/l/files", metadata={"attachments": [
"https://attachments.f95zone.to/2026/08/one.jpg",
]},
)
target = FollowMediaResolver(lambda *_args: None).resolve(item, 0)
self.assertEqual(target.url,
"https://attachments.f95zone.to/2026/08/one.jpg")
self.assertEqual(target.referer, item.url)
def test_f95_attachment_stays_available_while_other_links_need_a_session(self):
item = SimpleNamespace(
id=14, provider="f95zone", url="https://f95zone.to/threads/1/post-14",
media_url="https://f95zone.to/masked/gofile.io/1/locked",
metadata={"media_needs_credential": True, "attachments": [
"https://attachments.f95zone.to/2026/08/one.jpg",
]},
)
resolver = FollowMediaResolver(lambda *_args: None).with_credential_loader(
lambda provider: Credential(provider, {"cookie": "xf_session=saved"}))
target = resolver.resolve(item, 0)
self.assertEqual(target.url,
"https://attachments.f95zone.to/2026/08/one.jpg")
self.assertEqual(target.headers, {"Cookie": "xf_session=saved"})
def test_gofile_media_keeps_the_token_in_the_proxy_header(self):
item = SimpleNamespace(
id=12, provider="fanbox", url="https://creator.fanbox.cc/posts/12",
media_url=None, metadata={"media_items": [
{"url": "https://store1.gofile.io/download/one.mp4",
"resource_provider": "gofile"},
]},
)
resolver = FollowMediaResolver(lambda *_args: None).with_credential_loader(
lambda provider: Credential(provider, {"api_token": "secret"}))
target = resolver.resolve(item, 0)
self.assertEqual(target.headers, {"Authorization": "Bearer secret"})
self.assertNotIn("secret", target.url)
def test_the_resolution_cache_stays_bounded(self):
"""只有 rule34video 会进这个缓存,一次列表几百条;只按 TTL 过期的话,
进程活多久它就长多久。"""
def transport(request, timeout, max_bytes):
return HttpResponse(200, {}, b"<script>video_url: "
b"'https://rule34video.com/get_file/1_720p.mp4/'</script>")
resolver = FollowMediaResolver(transport, max_cache_entries=2)
for identifier in range(6):
resolver.resolve(SimpleNamespace(
id=identifier, provider="rule34video",
url=f"https://rule34video.com/video/{identifier}/x/",
media_url=None, metadata={}))
self.assertEqual(len(resolver._cache), 2)
class Rule34VideoUpstreamDedupeTests(unittest.TestCase):
"""播放器起播时同时问正片和清晰度,之后每个 Range 再问一遍;这些都只该换来一次详情页。"""
PAGE = b"<script>video_url: 'https://rule34video.com/get_file/1_720p.mp4/'</script>"
@staticmethod
def _item(identifier=7):
return SimpleNamespace(id=identifier, provider="rule34video",
url=f"https://rule34video.com/video/{identifier}/x/",
media_url=None, metadata={})
def test_concurrent_requests_for_one_item_share_a_single_detail_fetch(self):
requests = []
entered = threading.Event()
release = threading.Event()
def transport(request, timeout, max_bytes):
requests.append(request.url)
entered.set()
release.wait(5)
return HttpResponse(200, {}, self.PAGE)
resolver = FollowMediaResolver(transport)
results = []
workers = [threading.Thread(target=lambda: results.append(resolver.resolve(self._item())))
for _ in range(3)]
workers[0].start()
self.assertTrue(entered.wait(5))
for worker in workers[1:]:
worker.start()
release.set()
for worker in workers:
worker.join(5)
self.assertEqual(len(requests), 1)
self.assertEqual(len({result.url for result in results}), 1)
self.assertEqual(len(results), 3)
def test_a_missing_detail_page_is_not_refetched_within_the_ttl(self):
clock = [1000.0]
transport = mock.Mock(return_value=HttpResponse(404, {}, b""))
resolver = FollowMediaResolver(transport, clock=lambda: clock[0])
for _ in range(3):
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(self._item())
self.assertEqual(transport.call_count, 1)
clock[0] += resolver.MISS_TTL + 1
transport.return_value = HttpResponse(200, {}, self.PAGE)
self.assertIn("1_720p", resolver.resolve(self._item()).url)
self.assertEqual(transport.call_count, 2)
def test_a_rate_limited_source_pauses_every_item_until_retry_after(self):
clock = [1000.0]
transport = mock.Mock(return_value=HttpResponse(429, {"retry-after": "90"}, b""))
resolver = FollowMediaResolver(transport, clock=lambda: clock[0])
with self.assertLogs("peach.follow_stream", "WARNING"):
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(self._item(7))
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(self._item(8))
self.assertEqual(transport.call_count, 1)
self.assertGreater(resolver.cooling("rule34video"), 0)
clock[0] += 91
self.assertEqual(resolver.cooling("rule34video"), 0)
transport.return_value = HttpResponse(200, {}, self.PAGE)
self.assertIn("1_720p", resolver.resolve(self._item(8)).url)
self.assertEqual(transport.call_count, 2)
def test_retry_after_is_capped_and_missing_retry_after_uses_the_default(self):
clock = [0.0]
resolver = FollowMediaResolver(
mock.Mock(return_value=HttpResponse(403, {"Retry-After": "999999"}, b"")),
clock=lambda: clock[0])
with self.assertLogs("peach.follow_stream", "WARNING"), self.assertRaises(FollowMediaUnavailable):
resolver.resolve(self._item(7))
self.assertEqual(resolver.cooling("rule34video"), resolver.MAX_COOLDOWN)
resolver = FollowMediaResolver(mock.Mock(return_value=HttpResponse(429, {}, b"")),
clock=lambda: clock[0])
with self.assertLogs("peach.follow_stream", "WARNING"), self.assertRaises(FollowMediaUnavailable):
resolver.resolve(self._item(7))
self.assertEqual(resolver.cooling("rule34video"), resolver.COOLDOWN)
class Rule34VideoQualityTests(unittest.TestCase):
"""rule34video 把每档清晰度写成独立字段,只解析 video_url 会永远播最低那档。
2026-08-31 实测 video/4564733 的页面给出四档:
video_url -> 4564733_360.mp4
video_alt_url -> 4564733_480p.mp4
video_alt_url2 -> 4564733_720p.mp4
video_alt_url3 -> 4564733_1080p.mp4
"""
PAGE = (b"<script>"
b"video_url: 'https://rule34video.com/get_file/51/a/1/1_360.mp4/?v-acctoken=t0';"
b"video_alt_url: 'https://rule34video.com/get_file/51/a/1/1_480p.mp4/?v-acctoken=t1';"
b"video_alt_url2: 'https://rule34video.com/get_file/51/a/1/1_720p.mp4/?v-acctoken=t2';"
b"video_alt_url3: 'https://rule34video.com/get_file/51/a/1/1_1080p.mp4/?v-acctoken=t3';"
b"</script>")
def _resolver(self, body=None):
def transport(request, timeout, max_bytes):
return HttpResponse(200, {}, self.PAGE if body is None else body)
return FollowMediaResolver(transport)
def _item(self):
return SimpleNamespace(
id=11, provider="rule34video",
url="https://rule34video.com/video/11/x/",
media_url="https://rule34video.com/get_file/1.mp4/",
metadata={},
)
def test_every_quality_is_parsed_and_the_highest_plays_by_default(self):
resolved = self._resolver().resolve(self._item())
self.assertEqual([height for height, _ in resolved.qualities],
[1080, 720, 480, 360])
self.assertIn("1_1080p.mp4", resolved.url,
"默认应当是最高一档,旧实现固定播 360")
def test_a_requested_height_is_honoured(self):
resolved = self._resolver().resolve(self._item(), None, 480)
self.assertIn("1_480p.mp4", resolved.url)
def test_an_unavailable_height_falls_back_instead_of_failing(self):
"""签名 URL 会过期、来源也会改版;选档失败不该中断播放。"""
resolved = self._resolver().resolve(self._item(), None, 4320)
self.assertIn("1_1080p.mp4", resolved.url)
def test_other_providers_report_no_quality_choices(self):
item = SimpleNamespace(
id=12, provider="pawchive",
url="https://pawchive.pw/x",
media_url="https://pawchive.pw/data/a/b/c.mp4",
metadata={},
)
resolved = self._resolver(body=b"").resolve(item)
self.assertEqual(resolved.qualities, (),
"只有 rule34video 给多档,其余来源只显示原画")
class ProxyUpstreamTests(unittest.TestCase):
"""`/follow-stream` 的代理边界:跟哪些跳、转发哪些头、什么时候干脆不转发。
生产用的那个 client 开着 `follow_redirects=True`,所以这里的替身也开着:
要证明的正是「按跳自己判」而不是「靠 client 的默认值」。
"""
def _client(self, handler):
client = httpx.Client(transport=httpx.MockTransport(handler),
follow_redirects=True)
self.addCleanup(client.close)
return client
def test_a_redirect_out_of_the_whitelist_never_carries_the_credential(self):
"""上游只要回一个指向别处的 Location,httpx 就会把 Cookie 一路带过去。"""
seen = []
def handler(request):
seen.append(request)
return httpx.Response(302, request=request, headers={
"location": "https://collector.example/steal.jpg"})
target = ResolvedFollowMedia(
"https://attachments.f95zone.to/2026/08/one.jpg",
"https://f95zone.to/threads/1/post-1",
{"Cookie": "xf_session=fixture"},
allowed_hosts=("attachments.f95zone.to",))
with self.assertRaises(FollowProxyError):
open_upstream(self._client(handler), "GET", target, incoming={})
self.assertEqual([str(request.url) for request in seen],
["https://attachments.f95zone.to/2026/08/one.jpg"],
"出界的那一跳一次都不能发出去")
self.assertEqual(seen[0].headers["cookie"], "xf_session=fixture",
"第一跳确实带着会话,所以拦住的是真的泄露")
def test_a_redirect_inside_the_source_domain_is_still_followed(self):
"""归档站主域会 302 到取文件的节点(实测 kemono → n1.),必须跟得上。"""
def handler(request):
if request.url.host == "kemono.cr":
return httpx.Response(302, request=request, headers={
"location": "https://n1.kemono.cr/data/7.mp4"})
return httpx.Response(200, request=request,
stream=httpx.ByteStream(b"frames"),
headers={"content-type": "video/mp4"})
target = ResolvedFollowMedia("https://kemono.cr/data/7.mp4",
allowed_hosts=("kemono.cr",))
upstream = open_upstream(self._client(handler), "GET", target, incoming={})
self.addCleanup(upstream.close)
self.assertEqual(upstream.read(), b"frames")
def test_rule34video_is_followed_out_to_its_cdn(self):
"""rule34video 的 `/get_file/…` 只是签名入口,正片在 `*.boomio-cdn.com`。
这一跳没有凭据可泄露(这条媒体不带 `headers`),拦住它的唯一后果是每条
视频都停在 502。CDN 后缀登记在 `follow_providers` 的 `hosts` 里,代理照样
逐跳校验。
"""
def handler(request):
if request.url.host == "rule34video.com":
return httpx.Response(302, request=request, headers={
"location": "https://eu-cdn05-prem.boomio-cdn.com/x.mp4"})
return httpx.Response(206, request=request,
stream=httpx.ByteStream(b"frames"),
headers={"content-type": "video/mp4"})
target = ResolvedFollowMedia(
"https://rule34video.com/get_file/1/1080p.mp4/?v-acctoken=fresh",
"https://rule34video.com/video/1/work/",
allowed_hosts=("rule34video.com", "boomio-cdn.com"))
upstream = open_upstream(self._client(handler), "GET", target, incoming={})
self.addCleanup(upstream.close)
self.assertEqual(upstream.read(), b"frames")
def test_an_upstream_error_page_is_refused_instead_of_forwarded(self):
"""403 页面、限流提示、错误 JSON 转发给播放器没有用处,只会把上游的
主机名和提示语抄给浏览器。"""
def handler(request):
return httpx.Response(403, request=request,
content=b"<html>blocked by upstream shield</html>")
target = ResolvedFollowMedia("https://kemono.cr/data/7.mp4",
allowed_hosts=("kemono.cr",))
with self.assertRaises(FollowProxyError) as raised:
open_upstream(self._client(handler), "GET", target, incoming={})
self.assertIn("403", str(raised.exception))
self.assertEqual(raised.exception.status_code, 403)
self.assertNotIn("shield", str(raised.exception))
def test_a_redirect_loop_is_bounded(self):
seen = []
def handler(request):
seen.append(request)
return httpx.Response(302, request=request, headers={
"location": "https://kemono.cr/data/next.mp4"})
target = ResolvedFollowMedia("https://kemono.cr/data/7.mp4",
allowed_hosts=("kemono.cr",))
with self.assertRaises(FollowProxyError):
open_upstream(self._client(handler), "GET", target, incoming={})
self.assertEqual(len(seen), MAX_PROXY_REDIRECTS + 1)
def test_media_without_a_whitelist_is_not_proxied_at_all(self):
target = ResolvedFollowMedia("https://kemono.cr/data/7.mp4")
with self.assertRaises(FollowProxyError):
open_upstream(
self._client(lambda request: self.fail("不该发出请求")),
"GET", target, incoming={})
def test_public_host_media_follows_redirects_across_public_hosts(self):
"""图站常把原图 302 到自己的 CDN,公网模式下这一跳要跟得上。"""
def handler(request):
if request.url.host == "jpg5.su":
return httpx.Response(302, request=request, headers={
"location": "https://cdn.jpg5cdn.example/a.jpg"})
return httpx.Response(200, request=request,
stream=httpx.ByteStream(b"pixels"),
headers={"content-type": "image/jpeg"})
target = ResolvedFollowMedia("https://jpg5.su/a.jpg", public_hosts=True)
with mock.patch.object(peach_http, "host_addresses", return_value=("93.184.216.34",)):
upstream = open_upstream(self._client(handler), "GET", target, incoming={})
self.addCleanup(upstream.close)
self.assertEqual(upstream.read(), b"pixels")
self.assertNotIn("cookie", upstream.request.headers)
def test_public_host_media_never_reaches_into_the_lan(self):
"""域名字面上像公网但解析到内网,或者上游把我们指到内网地址:一次都不发。"""
seen = []
def to_lan(request):
seen.append(request)
return httpx.Response(302, request=request, headers={
"location": "https://169.254.169.254/admin.png"})
target = ResolvedFollowMedia("https://jpg5.su/a.jpg", public_hosts=True)
with mock.patch.object(peach_http, "host_addresses", return_value=("93.184.216.34",)):
with self.assertRaises(FollowProxyError):
open_upstream(self._client(to_lan), "GET", target, incoming={})
self.assertEqual([str(request.url) for request in seen], ["https://jpg5.su/a.jpg"])
for addresses in ((), ("127.0.0.1",), ("93.184.216.34", "fd00::1")):
with mock.patch.object(peach_http, "host_addresses", return_value=addresses):
with self.assertRaises(FollowProxyError, msg=str(addresses)):
open_upstream(self._client(lambda request: self.fail("不该发出请求")),
"GET", target, incoming={})
def test_only_range_headers_come_in_and_only_media_headers_go_back(self):
target = ResolvedFollowMedia(
"https://kemono.cr/data/7.mp4", "https://kemono.cr/post/7",
{"Cookie": "source_session=fixture"}, allowed_hosts=("kemono.cr",))
headers = proxy_request_headers(target, {
"range": "bytes=0-1", "accept": "video/*",
# 浏览器发给 Peach 的凭据不能跟着请求送到上游去。
"cookie": "peach=token", "authorization": "Bearer peach",
})
self.assertEqual(headers["Range"], "bytes=0-1")
self.assertEqual(headers["Accept"], "video/*")
self.assertEqual(headers["Referer"], "https://kemono.cr/post/7")
self.assertEqual(headers["Cookie"], "source_session=fixture")
self.assertNotIn("Authorization", headers)