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
This commit is contained in:
@@ -391,11 +391,10 @@ Post.initialize_post_sections = function() {
|
|||||||
|
|
||||||
Post.initialize_ugoira_player = function() {
|
Post.initialize_ugoira_player = function() {
|
||||||
if ($("#ugoira-controls").length) {
|
if ($("#ugoira-controls").length) {
|
||||||
let content_type = $("#image").data("ugoira-content-type");
|
let frame_delays = $("#image").data("ugoira-frame-delays");
|
||||||
let frames = $("#image").data("ugoira-frames");
|
|
||||||
let file_url = $(".image-container").data("file-url");
|
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);
|
$(window).on("resize.danbooru.ugoira_scale", Post.resize_ugoira_controls);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ require("jquery-ui/themes/base/slider.css");
|
|||||||
|
|
||||||
let Ugoira = {};
|
let Ugoira = {};
|
||||||
|
|
||||||
Ugoira.create_player = (mime_type, frames, file_url) => {
|
Ugoira.create_player = (frames, file_url) => {
|
||||||
var meta_data = {
|
var meta_data = {
|
||||||
mime_type: mime_type,
|
|
||||||
frames: frames
|
frames: frames
|
||||||
};
|
};
|
||||||
var options = {
|
var options = {
|
||||||
|
|||||||
30
app/javascript/vendor/pixiv-ugoira-player.js
vendored
30
app/javascript/vendor/pixiv-ugoira-player.js
vendored
@@ -89,7 +89,7 @@ function ZipImagePlayer(options) {
|
|||||||
this._loadingState = 0;
|
this._loadingState = 0;
|
||||||
this._dead = false;
|
this._dead = false;
|
||||||
this._context = options.canvas.getContext("2d");
|
this._context = options.canvas.getContext("2d");
|
||||||
this._files = {};
|
this._files = [];
|
||||||
this._frameCount = this.op.metadata.frames.length;
|
this._frameCount = this.op.metadata.frames.length;
|
||||||
this._debugLog("Frame count: " + this._frameCount);
|
this._debugLog("Frame count: " + this._frameCount);
|
||||||
this._frame = 0;
|
this._frame = 0;
|
||||||
@@ -257,7 +257,7 @@ ZipImagePlayer.prototype = {
|
|||||||
p += nameLen + extraLen + cmtLen;
|
p += nameLen + extraLen + cmtLen;
|
||||||
/*this._debugLog("File: " + name + " (" + uncompSize +
|
/*this._debugLog("File: " + name + " (" + uncompSize +
|
||||||
" bytes @ " + off + ")");*/
|
" bytes @ " + off + ")");*/
|
||||||
this._files[name] = {off: off, len: uncompSize};
|
this._files[i] = {off: off, len: uncompSize};
|
||||||
}
|
}
|
||||||
// Two outstanding fetches at any given time.
|
// Two outstanding fetches at any given time.
|
||||||
// Note: the implementation does not support more than two.
|
// Note: the implementation does not support more than two.
|
||||||
@@ -309,10 +309,10 @@ ZipImagePlayer.prototype = {
|
|||||||
var extraLen = dv.getUint16(28, true);
|
var extraLen = dv.getUint16(28, true);
|
||||||
return offset + 30 + nameLen + extraLen;
|
return offset + 30 + nameLen + extraLen;
|
||||||
},
|
},
|
||||||
_isFileAvailable: function(name) {
|
_isFileAvailable: function(index) {
|
||||||
var info = this._files[name];
|
var info = this._files[index];
|
||||||
if (!info) {
|
if (!info) {
|
||||||
this._error("File " + name + " not found in ZIP");
|
this._error("File " + index + " not found in ZIP");
|
||||||
}
|
}
|
||||||
if (this._pHead < (info.off + 30)) {
|
if (this._pHead < (info.off + 30)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -327,25 +327,24 @@ ZipImagePlayer.prototype = {
|
|||||||
if (frame >= this._frameCount) {
|
if (frame >= this._frameCount) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var meta = this.op.metadata.frames[frame];
|
|
||||||
if (!this.op.source) {
|
if (!this.op.source) {
|
||||||
// Unpacked mode (individiual frame URLs)
|
// Unpacked mode (individiual frame URLs)
|
||||||
this._loadFrame += 1;
|
this._loadFrame += 1;
|
||||||
this._loadImage(frame, meta.file, false);
|
this._loadImage(frame, frame, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this._isFileAvailable(meta.file)) {
|
if (!this._isFileAvailable(frame)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._loadFrame += 1;
|
this._loadFrame += 1;
|
||||||
var off = this._fileDataStart(this._files[meta.file].off);
|
var off = this._fileDataStart(this._files[frame].off);
|
||||||
var end = off + this._files[meta.file].len;
|
var end = off + this._files[frame].len;
|
||||||
var url;
|
var url;
|
||||||
var mime_type = this.op.metadata.mime_type || "image/png";
|
var mime_type = this.op.metadata.mime_type || "image/png";
|
||||||
if (this._URL) {
|
if (this._URL) {
|
||||||
var slice;
|
var slice;
|
||||||
if (!this._buf.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);
|
var view = new this._Uint8Array(slice);
|
||||||
view.set(this._bytes.subarray(off, end));
|
view.set(this._bytes.subarray(off, end));
|
||||||
} else {
|
} else {
|
||||||
@@ -374,9 +373,8 @@ ZipImagePlayer.prototype = {
|
|||||||
_loadImage: function(frame, url, isBlob) {
|
_loadImage: function(frame, url, isBlob) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var image = new Image();
|
var image = new Image();
|
||||||
var meta = this.op.metadata.frames[frame];
|
|
||||||
image.addEventListener('load', function() {
|
image.addEventListener('load', function() {
|
||||||
_this._debugLog("Loaded " + meta.file + " to frame " + frame);
|
_this._debugLog(`Loaded frame ${frame}`);
|
||||||
if (isBlob) {
|
if (isBlob) {
|
||||||
_this._URL.revokeObjectURL(url);
|
_this._URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
@@ -417,8 +415,8 @@ ZipImagePlayer.prototype = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var meta = this.op.metadata.frames[this._frame];
|
var delay = this.op.metadata.frames[this._frame];
|
||||||
this._debugLog("Displaying frame: " + this._frame + " " + meta.file);
|
this._debugLog("Displaying frame: " + this._frame);
|
||||||
var image = this._frameImages[this._frame];
|
var image = this._frameImages[this._frame];
|
||||||
if (!image) {
|
if (!image) {
|
||||||
this._debugLog("Image not available!");
|
this._debugLog("Image not available!");
|
||||||
@@ -444,7 +442,7 @@ ZipImagePlayer.prototype = {
|
|||||||
this._timer = setTimeout(function() {
|
this._timer = setTimeout(function() {
|
||||||
_this._timer = null;
|
_this._timer = null;
|
||||||
_this._nextFrame.apply(_this);
|
_this._nextFrame.apply(_this);
|
||||||
}, meta.delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_nextFrame: function(frame) {
|
_nextFrame: function(frame) {
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ class PixivUgoiraFrameData < ApplicationRecord
|
|||||||
q.apply_default_order(params)
|
q.apply_default_order(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def frame_delays
|
||||||
|
data.pluck("delay")
|
||||||
|
end
|
||||||
|
|
||||||
def normalize_data
|
def normalize_data
|
||||||
return if data.nil?
|
return if data.nil?
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
:class => "fit-width",
|
:class => "fit-width",
|
||||||
:width => post.image_width,
|
:width => post.image_width,
|
||||||
:height => post.image_height,
|
:height => post.image_height,
|
||||||
"data-ugoira-frames" => post.pixiv_ugoira_frame_data.data.to_json,
|
"data-ugoira-frame-delays" => post.pixiv_ugoira_frame_data.frame_delays.to_json,
|
||||||
"data-ugoira-content-type" => post.pixiv_ugoira_frame_data.content_type.to_json,
|
|
||||||
) %>
|
) %>
|
||||||
|
|
||||||
<div id="ugoira-controls">
|
<div id="ugoira-controls">
|
||||||
|
|||||||
Reference in New Issue
Block a user