Files
djk/internal/app/playback_direct.go
T

133 lines
4.1 KiB
Go

package app
import (
"context"
"io"
"net/http"
"net/url"
"strings"
"time"
)
func (app *UIApp) allowPlaybackDirect(ctx context.Context, media *playbackMediaSession, source, origin string) bool {
allowed := false
for _, selected := range app.mediaResources().snapshot().DirectSources {
allowed = allowed || selected == accountSourceGroup(source)
}
if !allowed || media.local != "" || len(media.key) != 0 || len(media.media.HLSKey) != 0 || len(media.media.CENCKey) != 0 {
return false
}
parsed, err := url.Parse(media.media.URL)
page, pageErr := url.Parse(origin)
if err != nil || pageErr != nil || page.Host == "" || parsed.User != nil || !isProviderHTTPMediaURL(parsed.String()) || page.Scheme == "https" && parsed.Scheme != "https" {
return false
}
if media.plan.Player == "mp4" && (!media.info.MP4 || !media.info.Range) {
return false
}
if media.plan.Player == "hls" && len(media.media.Variants) > 0 {
return false
}
ctx, cancel := context.WithTimeout(ctx, 8*time.Second)
defer cancel()
client := &http.Client{Transport: app.downloader.client.Transport, CheckRedirect: func(request *http.Request, previous []*http.Request) error {
if len(previous) >= 5 || request.URL.User != nil || page.Scheme == "https" && request.URL.Scheme != "https" {
return http.ErrUseLastResponse
}
return nil
}}
seen := make(map[string]bool)
manifests := 0
var check func(string, bool) bool
check = func(address string, playlist bool) bool {
if seen[address] {
return true
}
if len(seen) >= 256 || ctx.Err() != nil {
return false
}
seen[address] = true
remote, err := url.Parse(address)
if err != nil || remote.User != nil || !isProviderHTTPMediaURL(address) || page.Scheme == "https" && remote.Scheme != "https" {
return false
}
release, err := app.mediaResources().acquire(ctx, "media", false)
if err != nil {
return false
}
method := http.MethodHead
if playlist || media.plan.Player == "mp4" {
method = http.MethodGet
}
request, err := http.NewRequestWithContext(ctx, method, address, nil)
if err != nil {
release()
return false
}
request.Header.Set("Origin", origin)
request.Header.Set("Accept-Encoding", "identity")
if !playlist && method == http.MethodGet {
request.Header.Set("Range", "bytes=0-0")
}
response, err := client.Do(request)
if err != nil {
release()
return false
}
valid := response.StatusCode >= 200 && response.StatusCode < 300
if media.plan.Player == "mp4" {
valid = response.StatusCode == http.StatusPartialContent && strings.HasPrefix(response.Header.Get("Content-Range"), "bytes 0-0/")
}
if media.plan.Player == "hls" {
cors := response.Header.Get("Access-Control-Allow-Origin")
valid = valid && (cors == "*" || cors == origin)
}
var body []byte
if valid && playlist {
manifests++
body, err = io.ReadAll(io.LimitReader(response.Body, 256*1024+1))
valid = err == nil && len(body) <= 256*1024 && manifests <= 8 && strings.HasPrefix(strings.TrimSpace(strings.TrimPrefix(string(body), "\ufeff")), "#EXTM3U")
}
response.Body.Close()
release()
if !valid || !playlist {
return valid
}
base := response.Request.URL
nextPlaylist := false
resolve := func(reference string, childPlaylist bool) bool {
child, err := url.Parse(reference)
return err == nil && check(base.ResolveReference(child).String(), childPlaylist)
}
for _, line := range strings.Split(string(body), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
if strings.HasPrefix(line, "#EXT-X-STREAM-INF:") {
nextPlaylist = true
continue
}
if !strings.HasPrefix(line, "#") {
if !resolve(line, nextPlaylist) {
return false
}
nextPlaylist = false
continue
}
for _, tag := range []string{"#EXT-X-MEDIA:", "#EXT-X-I-FRAME-STREAM-INF:", "#EXT-X-KEY:", "#EXT-X-SESSION-KEY:", "#EXT-X-MAP:"} {
if !strings.HasPrefix(line, tag) {
continue
}
for _, match := range hlsURIAttribute.FindAllStringSubmatch(line, -1) {
if !resolve(match[1], tag == "#EXT-X-MEDIA:" || tag == "#EXT-X-I-FRAME-STREAM-INF:") {
return false
}
}
}
}
return true
}
return check(media.media.URL, media.plan.Player == "hls")
}