fix(videoplayback): Use HEAD requests to get the location of the videoplayback URL before doing a POST
All checks were successful
CI / build (push) Successful in 1m1s

"RFC 1945 and RFC 2068 specify that the client is not allowed to change
the method on the redirected request. However, most existing user agent
implementations treat 302 as if it were a 303 response, performing a GET
on the Location field-value regardless of the original request method.
The status codes 303 and 307 have been added for servers that wish to
make unambiguously clear which kind of reaction is expected of the
client."

Before doing this, POST requests that got a 302 status code, get
converted automatically to GET requests by the standard, which should
not happen. That is why Invidious does 5 HEAD requests to get the
Location header and send a correct URL on the POST request (NOTE:
INVIDIOUS UPSTREAMS STILL USES GET REQUESTS TO GET THE VIDEO FROM
YOUTUBE, THAT IS SUBJECT TO CHANGE with https://github.com/iv-org/invidious/issues/5034:

164d764d55/src/invidious/routes/video_playback.cr (L48-L78)

Due to this the redirects, the Host header can also change, so if the
stream is open for a long time and it gets redirected to another URL,
the Host header used the old Host header instead of the new one returned
by the Location header on the HEAD request to googlevideo.com, making
the request fail.

I hope this shit works tho
This commit is contained in:
Fijxu 2025-02-19 16:46:16 -03:00
parent 197a807b90
commit 319991c7b8
Signed by: Fijxu
GPG key ID: 32C1DDF333EDA6A4
2 changed files with 59 additions and 38 deletions

View file

@ -63,25 +63,25 @@ func videoplayback(w http.ResponseWriter, req *http.Request) {
host := q.Get("host")
q.Del("host")
if len(host) <= 0 {
// Fallback to use mvi and mn to build a host
mvi := q.Get("mvi")
mn := strings.Split(q.Get("mn"), ",")
// if len(host) <= 0 {
// // Fallback to use mvi and mn to build a host
// mvi := q.Get("mvi")
// mn := strings.Split(q.Get("mn"), ",")
if len(mvi) <= 0 {
w.WriteHeader(400)
io.WriteString(w, "'mvi' query string undefined")
return
}
// if len(mvi) <= 0 {
// w.WriteHeader(400)
// io.WriteString(w, "'mvi' query string undefined")
// return
// }
if len(mn) <= 0 {
w.WriteHeader(400)
io.WriteString(w, "'mn' query string undefined")
return
}
// if len(mn) <= 0 {
// w.WriteHeader(400)
// io.WriteString(w, "'mn' query string undefined")
// return
// }
host = "rr" + mvi + "---" + mn[0] + ".googlevideo.com"
}
// host = "rr" + mvi + "---" + mn[0] + ".googlevideo.com"
// }
parts := strings.Split(strings.ToLower(host), ".")
if len(parts) < 2 {
@ -124,45 +124,63 @@ func videoplayback(w http.ResponseWriter, req *http.Request) {
// https://github.com/FreeTubeApp/FreeTube/blob/5a4cd981cdf2c2a20ab68b001746658fd0c6484e/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js#L1097
body := []byte{0x78, 0} // protobuf body
request, err := http.NewRequest("POST", proxyURL.String(), bytes.NewReader(body))
postRequest, err := http.NewRequest("POST", proxyURL.String(), bytes.NewReader(body))
if err != nil {
log.Panic(err)
log.Panic("Failed to create postRequest:", err)
}
copyHeaders(req.Header, request.Header, false)
headRequest, err := http.NewRequest("HEAD", proxyURL.String(), nil)
if err != nil {
log.Panic("Failed to create headRequest:", err)
}
copyHeaders(req.Header, postRequest.Header, false)
copyHeaders(req.Header, headRequest.Header, false)
switch c {
case "ANDROID":
request.Header.Set("User-Agent", "com.google.android.youtube/1537338816 (Linux; U; Android 13; en_US; ; Build/TQ2A.230505.002; Cronet/113.0.5672.24)")
postRequest.Header.Set("User-Agent", "com.google.android.youtube/1537338816 (Linux; U; Android 13; en_US; ; Build/TQ2A.230505.002; Cronet/113.0.5672.24)")
headRequest.Header.Set("User-Agent", "com.google.android.youtube/1537338816 (Linux; U; Android 13; en_US; ; Build/TQ2A.230505.002; Cronet/113.0.5672.24)")
case "IOS":
request.Header.Set("User-Agent", "com.google.ios.youtube/19.32.8 (iPhone14,5; U; CPU iOS 17_6 like Mac OS X;)")
postRequest.Header.Set("User-Agent", "com.google.ios.youtube/19.32.8 (iPhone14,5; U; CPU iOS 17_6 like Mac OS X;)")
headRequest.Header.Set("User-Agent", "com.google.ios.youtube/19.32.8 (iPhone14,5; U; CPU iOS 17_6 like Mac OS X;)")
case "WEB":
request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
postRequest.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
headRequest.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
default:
request.Header.Set("User-Agent", default_ua)
postRequest.Header.Set("User-Agent", default_ua)
headRequest.Header.Set("User-Agent", default_ua)
}
request.Header.Add("Origin", "https://www.youtube.com")
request.Header.Add("Referer", "https://www.youtube.com/")
postRequest.Header.Add("Origin", "https://www.youtube.com")
headRequest.Header.Add("Origin", "https://www.youtube.com")
postRequest.Header.Add("Referer", "https://www.youtube.com/")
headRequest.Header.Add("Referer", "https://www.youtube.com/")
if connectionChecker(req.Context()) {
return
}
resp, err := client.Do(request)
resp := &http.Response{}
for i := 0; i < 5; i++ {
resp, err = client.Do(headRequest)
if err != nil {
log.Panic(err)
log.Panic("Failed to do HEAD request:", err)
}
if resp.Header.Get("Location") != "" {
new_url, _ := url.Parse(resp.Header.Get("Location"))
postRequest.URL = new_url
headRequest.URL = new_url
postRequest.Host = new_url.Host
headRequest.Host = new_url.Host
continue
} else {
break
}
}
if resp.Header.Get("location") != "" {
new_url, err := url.Parse(resp.Header.Get("location"))
resp, err = client.Do(postRequest)
if err != nil {
log.Panic(err)
}
request.URL = new_url
resp, err = client.Do(request)
if err != nil {
log.Panic(err)
}
log.Panic("Failed to do POST request:", err)
}
if err := forbiddenChecker(resp, w); err != nil {

View file

@ -48,6 +48,9 @@ var proxy string
// http/2 client
var h2client = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Transport: &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
var net string