From b388c735408af1a78e8f6165fa3af31bc623ef8f Mon Sep 17 00:00:00 2001 From: Umar Getagazov Date: Fri, 20 Oct 2023 00:18:25 +0300 Subject: [PATCH] ytdl_hook: parse the separate cookies field https://github.com/yt-dlp/yt-dlp/security/advisories/GHSA-v8mc-9377-rwjj https://github.com/yt-dlp/yt-dlp/commit/1ceb657bdd25 --- player/lua/ytdl_hook.lua | 105 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua index 19a0467f76..8e7d910144 100644 --- a/player/lua/ytdl_hook.lua +++ b/player/lua/ytdl_hook.lua @@ -24,6 +24,7 @@ options.read_options(o, nil, function() end) local chapter_list = {} +local playlist_cookies = {} function Set (t) local set = {} @@ -138,6 +139,78 @@ local function set_http_headers(http_headers) end end +local special_cookie_field_names = Set { + "expires", "max-age", "domain", "path" +} + +-- parse single-line Set-Cookie syntax +local function parse_cookies(cookies_line) + if not cookies_line then + return {} + end + local cookies = {} + local cookie = {} + for stem in cookies_line:gmatch('[^;]+') do + stem = stem:gsub("^%s*(.-)%s*$", "%1") + local name, value = stem:match('^(.-)=(.+)$') + if name and name ~= "" and value then + local cmp_name = name:lower() + if special_cookie_field_names[cmp_name] then + cookie[cmp_name] = value + else + if cookie.name and cookie.value then + table.insert(cookies, cookie) + end + cookie = { + name = name, + value = value, + } + end + end + end + if cookie.name and cookie.value then + local cookie_key = cookie.domain .. ":" .. cookie.name + cookies[cookie_key] = cookie + end + return cookies +end + +-- serialize cookies for avformat +local function serialize_cookies_for_avformat(cookies) + local result = '' + for _, cookie in pairs(cookies) do + local cookie_str = ('%s=%s; '):format(cookie.name, cookie.value) + for k, v in pairs(cookie) do + if k ~= "name" and k ~= "value" then + cookie_str = cookie_str .. ('%s=%s; '):format(k, v) + end + end + result = result .. cookie_str .. '\r\n' + end + return result +end + +-- set file-local cookies, preserving existing ones +local function set_cookies(cookies) + if not cookies or cookies == "" then + return + end + + local option_key = "file-local-options/stream-lavf-o" + local stream_opts = mp.get_property_native(option_key, {}) + local existing_cookies = parse_cookies(stream_opts["cookies"]) + + local new_cookies = parse_cookies(cookies) + for cookie_key, cookie in pairs(new_cookies) do + if not existing_cookies[cookie_key] then + existing_cookies[cookie_key] = cookie + end + end + + stream_opts["cookies"] = serialize_cookies_for_avformat(existing_cookies) + mp.set_property_native(option_key, stream_opts) +end + local function append_libav_opt(props, name, value) if not props then props = {} @@ -553,6 +626,9 @@ local function add_single_video(json) local http_headers = has_requested_formats and requested_formats[1].http_headers or json.http_headers + local cookies = has_requested_formats + and requested_formats[1].cookies + or json.cookies if o.use_manifests and valid_manifest(json) then -- prefer manifest_url if present @@ -740,6 +816,15 @@ local function add_single_video(json) "http_proxy", json.proxy) end + if cookies and cookies ~= "" then + local existing_cookies = parse_cookies(stream_opts["cookies"]) + local new_cookies = parse_cookies(cookies) + for cookie_key, cookie in pairs(new_cookies) do + existing_cookies[cookie_key] = cookie + end + stream_opts["cookies"] = serialize_cookies_for_avformat(existing_cookies) + end + mp.set_property_native("file-local-options/stream-lavf-o", stream_opts) end @@ -938,6 +1023,7 @@ function run_ytdl_hook(url) -- can't change the http headers for each entry, so use the 1st set_http_headers(json.entries[1].http_headers) + set_cookies(json.entries[1].cookies or json.cookies) mp.set_property("stream-open-filename", playlist) if json.title and mp.get_property("force-media-title", "") == "" then @@ -999,15 +1085,24 @@ function run_ytdl_hook(url) site = entry["webpage_url"] end + local playlist_url = nil + -- links without protocol as returned by --flat-playlist if not site:find("://") then -- youtube extractor provides only IDs, -- others come prefixed with the extractor name and ":" local prefix = site:find(":") and "ytdl://" or "https://youtu.be/" + playlist_url = prefix .. site table.insert(playlist, prefix .. site) elseif url_is_safe(site) then - table.insert(playlist, site) + playlist_url = site + end + + if playlist_url then + table.insert(playlist, playlist_url) + -- save the cookies in a table for the playlist hook + playlist_cookies[playlist_url] = entry.cookies or json.cookies end end @@ -1038,6 +1133,14 @@ if (not o.try_ytdl_first) then end) end +mp.add_hook("on_load", 20, function () + msg.verbose('playlist hook') + local url = mp.get_property("stream-open-filename", "") + if playlist_cookies[url] then + set_cookies(playlist_cookies[url]) + end +end) + mp.add_hook(o.try_ytdl_first and "on_load" or "on_load_fail", 10, function() msg.verbose('full hook') local url = mp.get_property("stream-open-filename", "")