From 319991c7b8e3bdbd67cfd1dc532f3306312d72da Mon Sep 17 00:00:00 2001 From: Fijxu Date: Wed, 19 Feb 2025 16:46:16 -0300 Subject: [PATCH] fix(videoplayback): Use HEAD requests to get the location of the videoplayback URL before doing a POST "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: https://github.com/iv-org/invidious/blob/164d764d553921e6ee98facda241f13c2103ec90/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 --- httppaths.go | 94 +++++++++++++++++++++++++++++++--------------------- main.go | 3 ++ 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/httppaths.go b/httppaths.go index 9e9713a..2cf1dc2 100644 --- a/httppaths.go +++ b/httppaths.go @@ -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) - if err != nil { - log.Panic(err) + resp := &http.Response{} + + for i := 0; i < 5; i++ { + resp, err = client.Do(headRequest) + if err != nil { + 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")) - if err != nil { - log.Panic(err) - } - request.URL = new_url - resp, err = client.Do(request) - if err != nil { - log.Panic(err) - } + resp, err = client.Do(postRequest) + if err != nil { + log.Panic("Failed to do POST request:", err) } if err := forbiddenChecker(resp, w); err != nil { diff --git a/main.go b/main.go index b5985d1..4c28b8f 100644 --- a/main.go +++ b/main.go @@ -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