Skip to content

Commit 9c282cc

Browse files
authored
Merge pull request #1 from tces1/codex/fix-emby-auth-token
Fix missing Emby auth token in transcode URLs
2 parents f537133 + 4e486a9 commit 9c282cc

3 files changed

Lines changed: 89 additions & 3 deletions

File tree

internal/proxy/server.go

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http/httputil"
1111
"net/url"
1212
"path"
13+
"regexp"
1314
"strconv"
1415
"strings"
1516
"time"
@@ -31,6 +32,8 @@ type Server struct {
3132
transcodeManager *transcode.Manager
3233
}
3334

35+
var embyTokenPattern = regexp.MustCompile(`(?i)\btoken\s*=\s*"?([^",\s]+)"?`)
36+
3437
func New(cfg config.Config) (*Server, error) {
3538
return NewWithTransport(cfg, http.DefaultTransport)
3639
}
@@ -193,7 +196,7 @@ func (s *Server) handlePlaybackInfo(w http.ResponseWriter, r *http.Request) {
193196
itemID := itemIDFromPlaybackPath(r.URL.Path)
194197
publicURL := s.publicURL(r)
195198
if strings.Contains(resp.Header.Get("Content-Type"), "application/json") && itemID != "" {
196-
rewritten, changed, report, err := emby.RewritePlaybackInfoWithReport(respBody, itemID, publicURL, r.URL.RawQuery)
199+
rewritten, changed, report, err := emby.RewritePlaybackInfoWithReport(respBody, itemID, publicURL, authAwareRawQuery(r))
197200
if err != nil {
198201
logging.Errorf("playbackinfo rewrite error item=%s err=%v", itemID, err)
199202
}
@@ -275,6 +278,7 @@ func (s *Server) prewarmPlaybackInfoTranscode(r *http.Request, report emby.Rewri
275278
inputURL := prewarmTranscodeInputURL(s.upstream, itemID, source.ID, audioStreamIndex, r)
276279
request := transcode.Request{
277280
InputURL: inputURL,
281+
Headers: r.Header.Clone(),
278282
ItemID: itemID,
279283
MediaSourceID: source.ID,
280284
PlaySessionID: source.SessionID,
@@ -317,7 +321,7 @@ func preferredAudioStreamIndex(streams []emby.AudioStreamReport) int {
317321
func prewarmTranscodeInputURL(upstream *url.URL, id string, mediaSourceID string, audioStreamIndex int, r *http.Request) string {
318322
u := *upstream
319323
u.Path = singleJoiningSlash(upstream.Path, path.Join("/emby/Videos", id, "stream"))
320-
query := r.URL.Query()
324+
query := authAwareQuery(r)
321325
query.Del("reqformat")
322326
query.Set("AutoOpenLiveStream", "true")
323327
query.Set("IsPlayback", "true")
@@ -334,12 +338,50 @@ func prewarmTranscodeInputURL(upstream *url.URL, id string, mediaSourceID string
334338
func transcodeInputURL(upstream *url.URL, id string, r *http.Request) string {
335339
u := *upstream
336340
u.Path = singleJoiningSlash(upstream.Path, path.Join("/emby/Videos", id, "stream"))
337-
query := r.URL.Query()
341+
query := authAwareQuery(r)
338342
query.Del("reqformat")
339343
u.RawQuery = query.Encode()
340344
return u.String()
341345
}
342346

347+
func authAwareRawQuery(r *http.Request) string {
348+
return authAwareQuery(r).Encode()
349+
}
350+
351+
func authAwareQuery(r *http.Request) url.Values {
352+
query := r.URL.Query()
353+
if query.Get("X-Emby-Token") == "" {
354+
if token := embyTokenFromHeaders(r.Header); token != "" {
355+
query.Set("X-Emby-Token", token)
356+
}
357+
}
358+
return query
359+
}
360+
361+
func embyTokenFromHeaders(headers http.Header) string {
362+
for _, key := range []string{"X-Emby-Token", "X-MediaBrowser-Token"} {
363+
if value := strings.TrimSpace(headers.Get(key)); value != "" {
364+
return value
365+
}
366+
}
367+
for _, key := range []string{"X-Emby-Authorization", "Authorization"} {
368+
if value := strings.TrimSpace(headers.Get(key)); value != "" {
369+
if token := extractEmbyToken(value); token != "" {
370+
return token
371+
}
372+
}
373+
}
374+
return ""
375+
}
376+
377+
func extractEmbyToken(value string) string {
378+
match := embyTokenPattern.FindStringSubmatch(value)
379+
if len(match) == 2 {
380+
return match[1]
381+
}
382+
return ""
383+
}
384+
343385
func (s *Server) publicURL(r *http.Request) string {
344386
if s.cfg.Server.PublicURL != "" {
345387
return s.cfg.Server.PublicURL

internal/proxy/server_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,35 @@ func TestNormalRequestsAreProxied(t *testing.T) {
110110
}
111111
}
112112

113+
func TestPlaybackInfoCarriesHeaderTokenIntoRewrittenTranscodeURL(t *testing.T) {
114+
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
115+
return jsonResponse(`{"MediaSources":[{"Id":"source1","SupportsDirectPlay":true,"MediaStreams":[{"Type":"Video","Codec":"hevc","Width":3840,"Height":2160},{"Type":"Audio","Codec":"dts","Channels":6}]}]}`), nil
116+
})
117+
118+
cfg := config.Default()
119+
cfg.Upstream.URL = "http://upstream.local"
120+
cfg.Server.PublicURL = "http://proxy.local"
121+
cfg.Clients = []config.ClientProfile{{Name: "yamby", Match: []string{"Yamby"}, Transcode: true}}
122+
123+
srv, err := proxy.NewWithTransport(cfg, transport)
124+
if err != nil {
125+
t.Fatal(err)
126+
}
127+
128+
req := httptest.NewRequest("GET", "/emby/Items/item123/PlaybackInfo?AutoOpenLiveStream=false&IsPlayback=false", nil)
129+
req.Header.Set("User-Agent", "Yamby TV")
130+
req.Header.Set("X-Emby-Authorization", `MediaBrowser Client="Emby for Android TV", Token="abc"`)
131+
rec := httptest.NewRecorder()
132+
srv.ServeHTTP(rec, req)
133+
134+
if rec.Code != http.StatusOK {
135+
t.Fatalf("status = %d body = %s", rec.Code, rec.Body.String())
136+
}
137+
if !strings.Contains(rec.Body.String(), "X-Emby-Token=abc") {
138+
t.Fatalf("rewritten body should carry header token: %s", rec.Body.String())
139+
}
140+
}
141+
113142
type roundTripFunc func(*http.Request) (*http.Response, error)
114143

115144
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {

internal/proxy/transcode_input_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,18 @@ func TestTranscodeInputURLStripsReqFormatFromStreamRequest(t *testing.T) {
6060
t.Fatalf("input url should keep stream parameters: %s", got)
6161
}
6262
}
63+
64+
func TestTranscodeInputURLUsesHeaderTokenWhenQueryIsMissing(t *testing.T) {
65+
upstream, err := url.Parse("http://upstream.local")
66+
if err != nil {
67+
t.Fatal(err)
68+
}
69+
req := httptest.NewRequest("GET", "/streambridge/transcode/item123/master.m3u8?MediaSourceId=source1", nil)
70+
req.Header.Set("X-Emby-Authorization", `MediaBrowser Client="Emby for Android TV", Token="abc"`)
71+
72+
got := transcodeInputURL(upstream, "item123", req)
73+
74+
if !strings.Contains(got, "X-Emby-Token=abc") {
75+
t.Fatalf("input url should carry token from auth header: %s", got)
76+
}
77+
}

0 commit comments

Comments
 (0)