From ebacb75cad1a40c8cca3d7a533e9743e6300ba50 Mon Sep 17 00:00:00 2001 From: Rogee Date: Wed, 7 Oct 2026 18:10:26 +0800 Subject: [PATCH] Use HTTPS for documented Bailian signed audio URLs --- internal/ai/bailian_tts.go | 21 +++++++++++++++++++-- internal/ai/bailian_tts_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/internal/ai/bailian_tts.go b/internal/ai/bailian_tts.go index 190ea3f..288373e 100644 --- a/internal/ai/bailian_tts.go +++ b/internal/ai/bailian_tts.go @@ -74,10 +74,11 @@ func synthesizeBailianTTS(ctx context.Context, approved TTSConfig, text string) if err := json.Unmarshal(raw, &result); err != nil || result.Output.Audio.URL == "" { return nil, errors.New("Bailian TTS response has no complete audio reference") } - if err := bailianURL(result.Output.Audio.URL, true); err != nil { + audioURL, err := bailianAudioDownloadURL(result.Output.Audio.URL) + if err != nil { return nil, errors.New("Bailian TTS audio reference is invalid") } - audioRequest, err := http.NewRequestWithContext(ctx, http.MethodGet, result.Output.Audio.URL, nil) + audioRequest, err := http.NewRequestWithContext(ctx, http.MethodGet, audioURL, nil) if err != nil { return nil, errors.New("create Bailian audio request") } @@ -159,6 +160,22 @@ func (w *boundedPCM) Write(data []byte) (int, error) { return w.Buffer.Write(data) } +// Bailian documents HTTP signed OSS links. Preserve the path and signature +// byte-for-byte, but fetch only over HTTPS; never fall back to HTTP. +func bailianAudioDownloadURL(raw string) (string, error) { + parsed, err := url.Parse(raw) + if err != nil { + return "", errors.New("invalid audio address") + } + if strings.HasPrefix(raw, "http://") && parsed.Host == "dashscope-result-bj.oss-cn-beijing.aliyuncs.com" && parsed.User == nil && parsed.Fragment == "" { + raw = "https" + raw[len("http"):] + } + if err := bailianURL(raw, true); err != nil { + return "", err + } + return raw, nil +} + func bailianURL(raw string, signedAudio bool) error { parsed, err := url.Parse(raw) if err != nil || parsed.Host == "" || parsed.User != nil || parsed.Fragment != "" { diff --git a/internal/ai/bailian_tts_test.go b/internal/ai/bailian_tts_test.go index 9a86678..1656dbd 100644 --- a/internal/ai/bailian_tts_test.go +++ b/internal/ai/bailian_tts_test.go @@ -51,6 +51,32 @@ func mockBailianTTS(t *testing.T, pcm []byte, observe func(string)) (string, *at return server.URL + "/api/v1/services/aigc/multimodal-generation/generation", calls } +func TestBailianAudioDownloadURLUpgradesDocumentedOSSHostOnly(t *testing.T) { + const from = "http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/ab%2Fcd.wav?Expires=123&Signature=fake%2Bvalue" + const want = "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/ab%2Fcd.wav?Expires=123&Signature=fake%2Bvalue" + got, err := bailianAudioDownloadURL(from) + if err != nil || got != want { + t.Fatalf("audio address = %q, %v; want exact HTTPS upgrade", got, err) + } + for _, address := range []string{ + "http://other.oss-cn-beijing.aliyuncs.com/audio.wav?Signature=fake", + "http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com:80/audio.wav?Signature=fake", + "http://user@dashscope-result-bj.oss-cn-beijing.aliyuncs.com/audio.wav?Signature=fake", + "http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/audio.wav#fragment", + } { + if _, err := bailianAudioDownloadURL(address); err == nil { + t.Fatalf("unapproved HTTP audio address accepted") + } + } + const alreadyHTTPS = "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/audio.wav?Signature=fake" + if got, err := bailianAudioDownloadURL(alreadyHTTPS); err != nil || got != alreadyHTTPS { + t.Fatalf("existing HTTPS address changed: %v", err) + } + if got, err := bailianAudioDownloadURL("http://127.0.0.1/audio.wav"); err != nil || got != "http://127.0.0.1/audio.wav" { + t.Fatalf("isolated local Mock address changed: %v", err) + } +} + func TestBailianFailureSummaryNeverLogsProviderPayload(t *testing.T) { for _, tc := range []struct { message, kind string