From b709941033a961bb2e5666f73635751fa6542bc4 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Fri, 28 Feb 2025 20:44:24 -0300 Subject: [PATCH] draft: replace HOST_URL by Host header --- config/config.example.yml | 10 ++++++++++ src/invidious/config.cr | 2 ++ src/invidious/routes/api/manifest.cr | 2 +- src/invidious/routes/api/v1/authenticated.cr | 4 ++-- src/invidious/routes/errors.cr | 2 +- src/invidious/routes/feeds.cr | 20 ++++++++++---------- src/invidious/routes/login.cr | 16 ++++++++++++++-- src/invidious/routes/preferences.cr | 16 ++++++++++++++-- src/invidious/routes/search.cr | 4 ++-- src/invidious/user/cookies.cr | 18 +++++++++++++++--- src/invidious/views/channel.ecr | 9 +++++---- src/invidious/views/watch.ecr | 16 ++++++++-------- 12 files changed, 84 insertions(+), 35 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index b04e0a30..54c21588 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -110,6 +110,16 @@ db: ## domain: +## +## Alternative domains. You can add other domains if you +## serve invidious on more than one domain, like Tor +## and I2P addresses. +## +## Accepted values: an array of fully qualified domain names (FQDN) +## Default: +## +#alternative_domains: [] + ## ## Tell Invidious that it is behind a proxy that provides only ## HTTPS, so all links must use the https:// scheme. This diff --git a/src/invidious/config.cr b/src/invidious/config.cr index 453256b5..58f4c5f9 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -105,6 +105,8 @@ class Config property hmac_key : String = "" # Domain to be used for links to resources on the site where an absolute URL is required property domain : String? + # Alternative domains. You can add other domains, like TOR and I2P addresses + property alternative_domains : Array(String) = [] of String # Subscribe to channels using PubSubHubbub (requires domain, hmac_key) property use_pubsub_feeds : Bool | Int32 = false property popular_enabled : Bool = true diff --git a/src/invidious/routes/api/manifest.cr b/src/invidious/routes/api/manifest.cr index 6c4225e5..3298b44f 100644 --- a/src/invidious/routes/api/manifest.cr +++ b/src/invidious/routes/api/manifest.cr @@ -209,7 +209,7 @@ module Invidious::Routes::API::Manifest raw_params["host"] = uri.host.not_nil! - "#{HOST_URL}/videoplayback?#{raw_params}" + "#{env.request.headers["Host"]}/videoplayback?#{raw_params}" end end diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr index a35d2f2b..18a82f6f 100644 --- a/src/invidious/routes/api/v1/authenticated.cr +++ b/src/invidious/routes/api/v1/authenticated.cr @@ -226,7 +226,7 @@ module Invidious::Routes::API::V1::Authenticated end playlist = create_playlist(title, privacy, user) - env.response.headers["Location"] = "#{HOST_URL}/api/v1/auth/playlists/#{playlist.id}" + env.response.headers["Location"] = "#{env.request.headers["Host"]}/api/v1/auth/playlists/#{playlist.id}" env.response.status_code = 201 { "title" => title, @@ -336,7 +336,7 @@ module Invidious::Routes::API::V1::Authenticated Invidious::Database::PlaylistVideos.insert(playlist_video) Invidious::Database::Playlists.update_video_added(plid, playlist_video.index) - env.response.headers["Location"] = "#{HOST_URL}/api/v1/auth/playlists/#{plid}/videos/#{playlist_video.index.to_u64.to_s(16).upcase}" + env.response.headers["Location"] = "#{env.request.headers["Host"]}/api/v1/auth/playlists/#{plid}/videos/#{playlist_video.index.to_u64.to_s(16).upcase}" env.response.status_code = 201 JSON.build do |json| diff --git a/src/invidious/routes/errors.cr b/src/invidious/routes/errors.cr index 1e9ab44e..5f0619d6 100644 --- a/src/invidious/routes/errors.cr +++ b/src/invidious/routes/errors.cr @@ -1,7 +1,7 @@ module Invidious::Routes::ErrorRoutes def self.error_404(env) # Workaround for #3117 - if HOST_URL.empty? && env.request.path.starts_with?("/v1/storyboards/sb") + if env.request.headers["Host"].empty? && env.request.path.starts_with?("/v1/storyboards/sb") return env.redirect "#{env.request.path[15..]}?#{env.params.query}" end diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr index 7f9a0edb..4cf8877f 100644 --- a/src/invidious/routes/feeds.cr +++ b/src/invidious/routes/feeds.cr @@ -199,21 +199,21 @@ module Invidious::Routes::Feeds xml.element("feed", "xmlns:yt": "http://www.youtube.com/xml/schemas/2015", "xmlns:media": "http://search.yahoo.com/mrss/", xmlns: "http://www.w3.org/2005/Atom", "xml:lang": "en-US") do - xml.element("link", rel: "self", href: "#{HOST_URL}#{env.request.resource}") + xml.element("link", rel: "self", href: "#{env.request.headers["Host"]}#{env.request.resource}") xml.element("id") { xml.text "yt:channel:#{ucid}" } xml.element("yt:channelId") { xml.text ucid } xml.element("title") { author } - xml.element("link", rel: "alternate", href: "#{HOST_URL}/channel/#{ucid}") + xml.element("link", rel: "alternate", href: "#{env.request.headers["Host"]}/channel/#{ucid}") xml.element("author") do xml.element("name") { xml.text author } - xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" } + xml.element("uri") { xml.text "#{env.request.headers["Host"]}/channel/#{ucid}" } end xml.element("image") do xml.element("url") { xml.text "" } xml.element("title") { xml.text author } - xml.element("link", rel: "self", href: "#{HOST_URL}#{env.request.resource}") + xml.element("link", rel: "self", href: "#{env.request.headers["Host"]}#{env.request.resource}") end videos.each do |video| @@ -255,9 +255,9 @@ module Invidious::Routes::Feeds xml.element("feed", "xmlns:yt": "http://www.youtube.com/xml/schemas/2015", "xmlns:media": "http://search.yahoo.com/mrss/", xmlns: "http://www.w3.org/2005/Atom", "xml:lang": "en-US") do - xml.element("link", "type": "text/html", rel: "alternate", href: "#{HOST_URL}/feed/subscriptions") + xml.element("link", "type": "text/html", rel: "alternate", href: "#{env.request.headers["Host"]}/feed/subscriptions") xml.element("link", "type": "application/atom+xml", rel: "self", - href: "#{HOST_URL}#{env.request.resource}") + href: "#{env.request.headers["Host"]}#{env.request.resource}") xml.element("title") { xml.text translate(locale, "Invidious Private Feed for `x`", user.email) } (notifications + videos).each do |video| @@ -286,11 +286,11 @@ module Invidious::Routes::Feeds xml.element("feed", "xmlns:yt": "http://www.youtube.com/xml/schemas/2015", "xmlns:media": "http://search.yahoo.com/mrss/", xmlns: "http://www.w3.org/2005/Atom", "xml:lang": "en-US") do - xml.element("link", rel: "self", href: "#{HOST_URL}#{env.request.resource}") + xml.element("link", rel: "self", href: "#{env.request.headers["Host"]}#{env.request.resource}") xml.element("id") { xml.text "iv:playlist:#{plid}" } xml.element("iv:playlistId") { xml.text plid } xml.element("title") { xml.text playlist.title } - xml.element("link", rel: "alternate", href: "#{HOST_URL}/playlist?list=#{plid}") + xml.element("link", rel: "alternate", href: "#{env.request.headers["Host"]}/playlist?list=#{plid}") xml.element("author") do xml.element("name") { xml.text playlist.author } @@ -314,7 +314,7 @@ module Invidious::Routes::Feeds when "url", "href" request_target = URI.parse(node[attribute.name]).request_target query_string_opt = request_target.starts_with?("/watch?v=") ? "&#{params}" : "" - node[attribute.name] = "#{HOST_URL}#{request_target}#{query_string_opt}" + node[attribute.name] = "#{env.request.headers["Host"]}#{request_target}#{query_string_opt}" else nil # Skip end end @@ -323,7 +323,7 @@ module Invidious::Routes::Feeds document = document.to_xml(options: XML::SaveOptions::NO_DECL) document.scan(/(?[^<]+)<\/uri>/).each do |match| - content = "#{HOST_URL}#{URI.parse(match["url"]).request_target}" + content = "#{env.request.headers["Host"]}#{URI.parse(match["url"]).request_target}" document = document.gsub(match[0], "#{content}") end document diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr index d0f7ac22..0e62f579 100644 --- a/src/invidious/routes/login.cr +++ b/src/invidious/routes/login.cr @@ -60,7 +60,13 @@ module Invidious::Routes::Login sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) Invidious::Database::SessionIDs.insert(sid, email) - env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid) + # Checks if there is any alternative domain, like a second domain name, + # TOR or I2P address + if alt = CONFIG.alternative_domains.index(env.request.headers["Host"]) + env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.alternative_domains[alt], sid) + else + env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid) + end else return error_template(401, "Wrong username or password") end @@ -163,7 +169,13 @@ module Invidious::Routes::Login view_name = "subscriptions_#{sha256(user.email)}" PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}") - env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid) + # Checks if there is any alternative domain, like a second domain name, + # TOR or I2P address + if alt = CONFIG.alternative_domains.index(env.request.headers["Host"]) + env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.alternative_domains[alt], sid) + else + env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid) + end if env.request.cookies["PREFS"]? user.preferences = env.get("preferences").as(Preferences) diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr index 39ca77c0..27b1c2a5 100644 --- a/src/invidious/routes/preferences.cr +++ b/src/invidious/routes/preferences.cr @@ -224,7 +224,13 @@ module Invidious::Routes::PreferencesRoute File.write("config/config.yml", CONFIG.to_yaml) end else - env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) + # Checks if there is any alternative domain, like a second domain name, + # TOR or I2P address + if alt = CONFIG.alternative_domains.index(env.request.headers["Host"]) + env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.alternative_domains[alt], preferences) + else + env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) + end end env.redirect referer @@ -259,7 +265,13 @@ module Invidious::Routes::PreferencesRoute preferences.dark_mode = "dark" end - env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) + # Checks if there is any alternative domain, like a second domain name, + # TOR or I2P address + if alt = CONFIG.alternative_domains.index(env.request.headers["Host"]) + env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.alternative_domains[alt], preferences) + else + env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) + end end if redirect diff --git a/src/invidious/routes/search.cr b/src/invidious/routes/search.cr index 44970922..283e8044 100644 --- a/src/invidious/routes/search.cr +++ b/src/invidious/routes/search.cr @@ -11,8 +11,8 @@ module Invidious::Routes::Search xml.element("LongName") { xml.text "Invidious Search" } xml.element("Description") { xml.text "Search for videos, channels, and playlists on Invidious" } xml.element("InputEncoding") { xml.text "UTF-8" } - xml.element("Image", width: 48, height: 48, type: "image/x-icon") { xml.text "#{HOST_URL}/favicon.ico" } - xml.element("Url", type: "text/html", method: "get", template: "#{HOST_URL}/search?q={searchTerms}") + xml.element("Image", width: 48, height: 48, type: "image/x-icon") { xml.text "#{env.request.headers["Host"]}/favicon.ico" } + xml.element("Url", type: "text/html", method: "get", template: "#{env.request.headers["Host"]}/search?q={searchTerms}") end end end diff --git a/src/invidious/user/cookies.cr b/src/invidious/user/cookies.cr index 654efc15..38c5fae1 100644 --- a/src/invidious/user/cookies.cr +++ b/src/invidious/user/cookies.cr @@ -6,17 +6,23 @@ struct Invidious::User # Note: we use ternary operator because the two variables # used in here are not booleans. - SECURE = (Kemal.config.ssl || CONFIG.https_only) ? true : false + @@secure = (Kemal.config.ssl || CONFIG.https_only) ? true : false # Session ID (SID) cookie # Parameter "domain" comes from the global config def sid(domain : String?, sid) : HTTP::Cookie + # Not secure if it's being accessed from I2P + # Browsers expect the domain to include https. On I2P there is no HTTPS + if domain.not_nil!.split(".").last == "i2p" + @@secure = false + end + return HTTP::Cookie.new( name: "SID", domain: domain, value: sid, expires: Time.utc + 2.years, - secure: SECURE, + secure: @@secure, http_only: true, samesite: HTTP::Cookie::SameSite::Lax ) @@ -25,12 +31,18 @@ struct Invidious::User # Preferences (PREFS) cookie # Parameter "domain" comes from the global config def prefs(domain : String?, preferences : Preferences) : HTTP::Cookie + # Not secure if it's being accessed from I2P + # Browsers expect the domain to include https. On I2P there is no HTTPS + if domain.not_nil!.split(".").last == "i2p" + @@secure = false + end + return HTTP::Cookie.new( name: "PREFS", domain: domain, value: URI.encode_www_form(preferences.to_json), expires: Time.utc + 2.years, - secure: SECURE, + secure: @@secure, http_only: false, samesite: HTTP::Cookie::SameSite::Lax ) diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr index 686de6bd..d9c26cd9 100644 --- a/src/invidious/views/channel.ecr +++ b/src/invidious/views/channel.ecr @@ -25,21 +25,22 @@ first_page: continuation.nil?, params: env.params.query, ) + host = env.request.headers["Host"] %> <% content_for "header" do %> <%- if selected_tab.videos? -%> - + - + - + - + <%- end -%> diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 6f9ced6f..b485dfae 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -1,29 +1,29 @@ <% ucid = video.ucid %> <% title = HTML.escape(video.title) %> <% author = HTML.escape(video.author) %> - +<% host = env.request.headers["Host"] %> <% content_for "header" do %> "> - + - + - - + + - + - - + +