From a138436e08c8fa59d2d7918e2f1b43f6e35c256e Mon Sep 17 00:00:00 2001 From: Fijxu Date: Mon, 14 Sep 2026 00:54:47 -0300 Subject: [PATCH] feat: sanity check video IDs with regex --- src/invidious/exceptions.cr | 12 ++++++++++++ src/invidious/helpers/utils.cr | 7 +++++++ src/invidious/routes/api/v1/authenticated.cr | 10 ++++++---- src/invidious/routes/api/v1/videos.cr | 5 +++-- src/invidious/routes/video_playback.cr | 4 ++-- src/invidious/routes/watch.cr | 2 +- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/invidious/exceptions.cr b/src/invidious/exceptions.cr index 690db9077..86ce20c25 100644 --- a/src/invidious/exceptions.cr +++ b/src/invidious/exceptions.cr @@ -38,3 +38,15 @@ end # some important informations, and that the query should be sent again. class RetryOnceException < Exception end + +# Exception for invalid video IDs. +class InvalidVideoID < InfoException + getter id : String? + + def initialize(@id) + end + + def message + return "Invalid video ID '#{id}'" + end +end diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 7a262f849..d8f74b386 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -404,3 +404,10 @@ def invidious_companion_encrypt(data) encrypted_data = encrypt_ecb_without_salt("#{timestamp}|#{data}", CONFIG.invidious_companion_key) return Base64.urlsafe_encode(encrypted_data) end + +def validate_video_id(id : String) : Bool + # This is the video ID regex. May be need to be changed + # if Youtube ever decides to add more characters to their + # video IDs. + /^[a-zA-Z0-9_-]{11}$/.matches?(id) +end diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr index fcefc1183..c5eaff921 100644 --- a/src/invidious/routes/api/v1/authenticated.cr +++ b/src/invidious/routes/api/v1/authenticated.cr @@ -81,9 +81,10 @@ module Invidious::Routes::API::V1::Authenticated return error_json(409, "Watch history is disabled in preferences.") end + # Sanity checks id = env.params.url["id"] - if !id.match(/^[a-zA-Z0-9_-]{11}$/) - return error_json(400, "Invalid video id.") + unless validate_video_id(id) + return error_json(400, InvalidVideoID.new(id)) end Invidious::Database::Users.mark_watched(user, id) @@ -309,8 +310,9 @@ module Invidious::Routes::API::V1::Authenticated end video_id = env.params.json["videoId"].try &.as(String) - if !video_id - return error_json(403, "Invalid videoId") + # Sanity checks + unless video_id && validate_video_id(video_id) + return error_json(403, InvalidVideoID.new(video_id)) end begin diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index 0a5d5a4ec..bd76b005b 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -32,8 +32,9 @@ module Invidious::Routes::API::V1::Videos id = env.params.url["id"] region = env.params.query["region"]? || env.params.body["region"]? - if id.nil? || id.size != 11 || !id.matches?(/^[\w-]+$/) - return error_json(400, "Invalid video ID") + # Sanity checks + unless validate_video_id(id) + return error_json(400, InvalidVideoID.new(id)) end # See https://github.com/ytdl-org/youtube-dl/blob/6ab30ff50bf6bd0585927cb73c7421bef184f87a/youtube_dl/extractor/youtube.py#L1354 diff --git a/src/invidious/routes/video_playback.cr b/src/invidious/routes/video_playback.cr index f16a5a58d..0d0cc8443 100644 --- a/src/invidious/routes/video_playback.cr +++ b/src/invidious/routes/video_playback.cr @@ -268,8 +268,8 @@ module Invidious::Routes::VideoPlayback itag = env.params.query["itag"]?.try &.to_i? # Sanity checks - if id.nil? || id.size != 11 || !id.matches?(/^[\w-]+$/) - return error_template(400, "Invalid video ID") + unless id && validate_video_id(id) + return error_json(400, InvalidVideoID.new(id)) end if !itag.nil? && (itag <= 0 || itag >= 1000) diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 3c66f1bc0..7f62a2a4f 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -235,7 +235,7 @@ module Invidious::Routes::Watch token = env.params.body["csrf_token"]? id = env.params.query["id"]? - if !id + unless id && validate_video_id(id) env.response.status_code = 400 return end