From 16f325006781bc7664901585a458ba832eefa915 Mon Sep 17 00:00:00 2001 From: evazion Date: Sun, 9 Oct 2022 16:31:53 -0500 Subject: [PATCH] ugoira: fix player to only use list of frame delays. The frame data for Ugoira files is stored like this: [{"file"=>"000000.jpg", "delay"=>65}, {"file"=>"000001.jpg", "delay"=>65}, {"file"=>"000002.jpg", "delay"=>65}, {"file"=>"000003.jpg", "delay"=>65}, {"file"=>"000004.jpg", "delay"=>65}, {"file"=>"000005.jpg", "delay"=>65}, {"file"=>"000006.jpg", "delay"=>65}, {"file"=>"000007.jpg", "delay"=>65}, {"file"=>"000008.jpg", "delay"=>65}, {"file"=>"000009.jpg", "delay"=>65}, {"file"=>"000010.jpg", "delay"=>65}] This is stored in the pixiv_ugoira_frame_data table in YAML format. This is a problem because a) we only need the frame delays to play the Ugoira, not the filenames, and b) storing the data in YAML format is a security issue that's blocking the upgrade to Rails 7.0.4 (see [1]). This commit changes the Ugoira Javascript player so that it only uses the list of frame delays, not the filenames, to play the Ugoira. This paves the way for storing the frame delays as a simple integer array instead as a serialized YAML object. This assumes that the images in a Ugoira zip file are stored in the same order they should be played back in. This was confirmed by checking every zip file and verifying that files are actually stored in filename order. [1]: https://discuss.rubyonrails.org/t/cve-2022-32224-possible-rce-escalation-bug-with-serialized-columns-in-active-record/81017 --- app/javascript/src/javascripts/posts.js | 5 ++-- app/javascript/src/javascripts/ugoira.js | 3 +- app/javascript/vendor/pixiv-ugoira-player.js | 30 +++++++++---------- app/models/pixiv_ugoira_frame_data.rb | 4 +++ .../partials/show/_ugoira_original.html.erb | 3 +- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/app/javascript/src/javascripts/posts.js b/app/javascript/src/javascripts/posts.js index f1c1ab9a2..8cf25f613 100644 --- a/app/javascript/src/javascripts/posts.js +++ b/app/javascript/src/javascripts/posts.js @@ -391,11 +391,10 @@ Post.initialize_post_sections = function() { Post.initialize_ugoira_player = function() { if ($("#ugoira-controls").length) { - let content_type = $("#image").data("ugoira-content-type"); - let frames = $("#image").data("ugoira-frames"); + let frame_delays = $("#image").data("ugoira-frame-delays"); let file_url = $(".image-container").data("file-url"); - Ugoira.create_player(content_type, frames, file_url); + Ugoira.create_player(frame_delays, file_url); $(window).on("resize.danbooru.ugoira_scale", Post.resize_ugoira_controls); } }; diff --git a/app/javascript/src/javascripts/ugoira.js b/app/javascript/src/javascripts/ugoira.js index afc5ec02a..e4af31534 100644 --- a/app/javascript/src/javascripts/ugoira.js +++ b/app/javascript/src/javascripts/ugoira.js @@ -6,9 +6,8 @@ require("jquery-ui/themes/base/slider.css"); let Ugoira = {}; -Ugoira.create_player = (mime_type, frames, file_url) => { +Ugoira.create_player = (frames, file_url) => { var meta_data = { - mime_type: mime_type, frames: frames }; var options = { diff --git a/app/javascript/vendor/pixiv-ugoira-player.js b/app/javascript/vendor/pixiv-ugoira-player.js index 2831f4ef1..1b8f3377a 100644 --- a/app/javascript/vendor/pixiv-ugoira-player.js +++ b/app/javascript/vendor/pixiv-ugoira-player.js @@ -89,7 +89,7 @@ function ZipImagePlayer(options) { this._loadingState = 0; this._dead = false; this._context = options.canvas.getContext("2d"); - this._files = {}; + this._files = []; this._frameCount = this.op.metadata.frames.length; this._debugLog("Frame count: " + this._frameCount); this._frame = 0; @@ -257,7 +257,7 @@ ZipImagePlayer.prototype = { p += nameLen + extraLen + cmtLen; /*this._debugLog("File: " + name + " (" + uncompSize + " bytes @ " + off + ")");*/ - this._files[name] = {off: off, len: uncompSize}; + this._files[i] = {off: off, len: uncompSize}; } // Two outstanding fetches at any given time. // Note: the implementation does not support more than two. @@ -309,10 +309,10 @@ ZipImagePlayer.prototype = { var extraLen = dv.getUint16(28, true); return offset + 30 + nameLen + extraLen; }, - _isFileAvailable: function(name) { - var info = this._files[name]; + _isFileAvailable: function(index) { + var info = this._files[index]; if (!info) { - this._error("File " + name + " not found in ZIP"); + this._error("File " + index + " not found in ZIP"); } if (this._pHead < (info.off + 30)) { return false; @@ -327,25 +327,24 @@ ZipImagePlayer.prototype = { if (frame >= this._frameCount) { return; } - var meta = this.op.metadata.frames[frame]; if (!this.op.source) { // Unpacked mode (individiual frame URLs) this._loadFrame += 1; - this._loadImage(frame, meta.file, false); + this._loadImage(frame, frame, false); return; } - if (!this._isFileAvailable(meta.file)) { + if (!this._isFileAvailable(frame)) { return; } this._loadFrame += 1; - var off = this._fileDataStart(this._files[meta.file].off); - var end = off + this._files[meta.file].len; + var off = this._fileDataStart(this._files[frame].off); + var end = off + this._files[frame].len; var url; var mime_type = this.op.metadata.mime_type || "image/png"; if (this._URL) { var slice; if (!this._buf.slice) { - slice = new this._ArrayBuffer(this._files[meta.file].len); + slice = new this._ArrayBuffer(this._files[frame].len); var view = new this._Uint8Array(slice); view.set(this._bytes.subarray(off, end)); } else { @@ -374,9 +373,8 @@ ZipImagePlayer.prototype = { _loadImage: function(frame, url, isBlob) { var _this = this; var image = new Image(); - var meta = this.op.metadata.frames[frame]; image.addEventListener('load', function() { - _this._debugLog("Loaded " + meta.file + " to frame " + frame); + _this._debugLog(`Loaded frame ${frame}`); if (isBlob) { _this._URL.revokeObjectURL(url); } @@ -417,8 +415,8 @@ ZipImagePlayer.prototype = { return; } var _this = this; - var meta = this.op.metadata.frames[this._frame]; - this._debugLog("Displaying frame: " + this._frame + " " + meta.file); + var delay = this.op.metadata.frames[this._frame]; + this._debugLog("Displaying frame: " + this._frame); var image = this._frameImages[this._frame]; if (!image) { this._debugLog("Image not available!"); @@ -444,7 +442,7 @@ ZipImagePlayer.prototype = { this._timer = setTimeout(function() { _this._timer = null; _this._nextFrame.apply(_this); - }, meta.delay); + }, delay); } }, _nextFrame: function(frame) { diff --git a/app/models/pixiv_ugoira_frame_data.rb b/app/models/pixiv_ugoira_frame_data.rb index b54349e83..de9b58a1b 100644 --- a/app/models/pixiv_ugoira_frame_data.rb +++ b/app/models/pixiv_ugoira_frame_data.rb @@ -16,6 +16,10 @@ class PixivUgoiraFrameData < ApplicationRecord q.apply_default_order(params) end + def frame_delays + data.pluck("delay") + end + def normalize_data return if data.nil? diff --git a/app/views/posts/partials/show/_ugoira_original.html.erb b/app/views/posts/partials/show/_ugoira_original.html.erb index cfe720603..6acae5fd5 100644 --- a/app/views/posts/partials/show/_ugoira_original.html.erb +++ b/app/views/posts/partials/show/_ugoira_original.html.erb @@ -5,8 +5,7 @@ :class => "fit-width", :width => post.image_width, :height => post.image_height, - "data-ugoira-frames" => post.pixiv_ugoira_frame_data.data.to_json, - "data-ugoira-content-type" => post.pixiv_ugoira_frame_data.content_type.to_json, + "data-ugoira-frame-delays" => post.pixiv_ugoira_frame_data.frame_delays.to_json, ) %>