mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 12:06:38 +08:00
fa41936095
后端: - /api/reader/media 签名媒体代理:HMAC-SHA256(jwtSecret, bookID|url) 防开放 中转滥用;免 JWT(audio/img 元素带不了头)但需有效签名+书籍存在 - 携书源级请求头与 Referer(防盗链)、Range 透传(音频拖动进度 206)、 m3u8 播放列表分片/密钥地址改写为签名代理(hls.js 无感续播) - 音频/图片地址按最终页面 URL 绝对化,imageStyle 透传; GetContentForBook 将 tracks/images 统一改写为代理地址 前端: - ReaderAudioPanel(仿 ReadAloudDialog):hls.js 播 m3u8 / <audio> 直链, 上一章/播放暂停/下一章、进度条拖动、倍速 0.75–2x、按秒记忆进度、 播完自动下一章、点背景呼出菜单 - ReaderComic:上下滚动 / 左右单页双模式(复用翻页/滚动切换)、 图片懒加载+失败占位、滚动模式左右点击滚屏/中间呼菜单、 单页模式点击区翻页、菜单进度条按图片序号、按图片序号记忆进度 - 菜单进度条按内容类型适配(文本页/音频章节/漫画图片序号)
44 lines
1.2 KiB
Go
44 lines
1.2 KiB
Go
package reader
|
|
|
|
import (
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/truewhile/MeBox/internal/config"
|
|
)
|
|
|
|
// TestProxyURLSignRoundTrip 验证媒体代理签名:往返还原 + 防篡改。
|
|
func TestProxyURLSignRoundTrip(t *testing.T) {
|
|
s := &ReaderService{cfg: &config.Config{Secrets: config.SecretsConfig{JWTSecret: "test-secret"}}}
|
|
const raw = "https://cdn.example.com/audio/ep1.mp3?token=abc"
|
|
signed := s.ProxyURL("book-1", raw)
|
|
|
|
parsed, err := url.Parse(signed)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
q := parsed.Query()
|
|
if q.Get("b") != "book-1" {
|
|
t.Fatalf("bookID = %q", q.Get("b"))
|
|
}
|
|
restored, err := s.VerifyProxyURL(q.Get("b"), q.Get("u"), q.Get("s"))
|
|
if err != nil {
|
|
t.Fatalf("verify failed: %v", err)
|
|
}
|
|
if restored != raw {
|
|
t.Fatalf("restored = %q", restored)
|
|
}
|
|
// 篡改签名必须被拒绝
|
|
if _, err := s.VerifyProxyURL(q.Get("b"), q.Get("u"), "00000000000000000000000000000000"); err == nil {
|
|
t.Fatal("expected signature rejection")
|
|
}
|
|
// 换书签必须被拒绝
|
|
if _, err := s.VerifyProxyURL("book-2", q.Get("u"), q.Get("s")); err == nil {
|
|
t.Fatal("expected cross-book signature rejection")
|
|
}
|
|
// 已是代理地址的不再二次包裹
|
|
if again := s.ProxyURL("book-1", signed); again != signed {
|
|
t.Fatalf("double wrap: %q", again)
|
|
}
|
|
}
|