1
0
mirror of https://github.com/janeczku/calibre-web synced 2025-12-07 00:38:06 +00:00

Changed backend for comic reader, now supports rar5

This commit is contained in:
Ozzie Isaacs
2021-05-16 08:35:20 +02:00
parent d95838309e
commit 3e5c944365
16 changed files with 10137 additions and 5543 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,90 @@
// Copyright (c) 2017 Matthew Brennan Jones <matthew.brennan.jones@gmail.com>
// This software is licensed under a MIT License
// https://github.com/workhorsy/uncompress.js
"use strict";
// Based on the information from:
// https://en.wikipedia.org/wiki/Tar_(computing)
(function() {
const TAR_TYPE_FILE = 0;
const TAR_TYPE_DIR = 5;
const TAR_HEADER_SIZE = 512;
const TAR_TYPE_OFFSET = 156;
const TAR_TYPE_SIZE = 1;
const TAR_SIZE_OFFSET = 124;
const TAR_SIZE_SIZE = 12;
const TAR_NAME_OFFSET = 0;
const TAR_NAME_SIZE = 100;
function _tarRead(view, offset, size) {
return view.slice(offset, offset + size);
}
function tarGetEntries(filename, array_buffer) {
let view = new Uint8Array(array_buffer);
let offset = 0;
let entries = [];
while (offset + TAR_HEADER_SIZE < view.byteLength) {
// Get entry name
let entry_name = saneMap(_tarRead(view, offset + TAR_NAME_OFFSET, TAR_NAME_SIZE), String.fromCharCode);
entry_name = entry_name.join('').replace(/\0/g, '');
// No entry name, so probably the last block
if (entry_name.length === 0) {
break;
}
// Get entry size
let entry_size = parseInt(saneJoin(saneMap(_tarRead(view, offset + TAR_SIZE_OFFSET, TAR_SIZE_SIZE), String.fromCharCode), ''), 8);
let entry_type = saneMap(_tarRead(view, offset + TAR_TYPE_OFFSET, TAR_TYPE_SIZE), String.fromCharCode) | 0;
// Save this as en entry if it is a file or directory
if (entry_type === TAR_TYPE_FILE || entry_type === TAR_TYPE_DIR) {
let entry = {
name: entry_name,
size: entry_size,
is_file: entry_type == TAR_TYPE_FILE,
offset: offset
};
entries.push(entry);
}
// Round the offset up to be divisible by TAR_HEADER_SIZE
offset += (entry_size + TAR_HEADER_SIZE);
if (offset % TAR_HEADER_SIZE > 0) {
let even = (offset / TAR_HEADER_SIZE) | 0; // number of times it goes evenly into TAR_HEADER_SIZE
offset = (even + 1) * TAR_HEADER_SIZE;
}
}
return entries;
}
function tarGetEntryData(entry, array_buffer) {
let view = new Uint8Array(array_buffer);
let offset = entry.offset;
let size = entry.size;
// Get entry data
let entry_data = _tarRead(view, offset + TAR_HEADER_SIZE, size);
return entry_data;
}
// Figure out if we are running in a Window or Web Worker
let scope = null;
if (typeof window === 'object') {
scope = window;
} else if (typeof importScripts === 'function') {
scope = self;
}
// Set exports
scope.tarGetEntries = tarGetEntries;
scope.tarGetEntryData = tarGetEntryData;
})();

View File

@@ -0,0 +1,420 @@
// Copyright (c) 2017 Matthew Brennan Jones <matthew.brennan.jones@gmail.com>
// This software is licensed under a MIT License
// https://github.com/workhorsy/uncompress.js
"use strict";
function loadScript(url, cb) {
// Window
if (typeof window === 'object') {
let script = document.createElement('script');
script.type = "text/javascript";
script.src = url;
script.onload = function() {
if (cb) cb();
};
document.head.appendChild(script);
// Web Worker
} else if (typeof importScripts === 'function') {
importScripts(url);
if (cb) cb();
}
}
function currentScriptPath() {
// NOTE: document.currentScript does not work in a Web Worker
// So we have to parse a stack trace maually
try {
throw new Error('');
} catch(e) {
let stack = e.stack;
let line = null;
// Chrome and IE
if (stack.indexOf('@') !== -1) {
line = stack.split('@')[1].split('\n')[0];
// Firefox
} else {
line = stack.split('(')[1].split(')')[0];
}
line = line.substring(0, line.lastIndexOf('/')) + '/';
return line;
}
}
// This is used by libunrar.js to load libunrar.js.mem
let unrarMemoryFileLocation = null;
let g_on_loaded_cb = null;
(function() {
let _loaded_archive_formats = [];
// Polyfill for missing array slice method (IE 11)
if (typeof Uint8Array !== 'undefined') {
if (! Uint8Array.prototype.slice) {
Uint8Array.prototype.slice = function(start, end) {
let retval = new Uint8Array(end - start);
let j = 0;
for (let i=start; i<end; ++i) {
retval[j] = this[i];
j++;
}
return retval;
};
}
}
// FIXME: This function is super inefficient
function saneJoin(array, separator) {
let retval = '';
for (let i=0; i<array.length; ++i) {
if (i === 0) {
retval += array[i];
} else {
retval += separator + array[i];
}
}
return retval;
}
function saneMap(array, cb) {
let retval = new Array(array.length);
for (let i=0; i<retval.length; ++i) {
retval[i] = cb(array[i]);
}
return retval;
}
function loadArchiveFormats(formats, cb) {
// Get the path of the current script
let path = currentScriptPath();
let load_counter = 0;
let checkForLoadDone = function() {
load_counter++;
// Get the total number of loads before we are done loading
// If loading RAR in a Window, have 1 extra load.
let load_total = formats.length;
if (formats.indexOf('rar') !== -1 && typeof window === 'object') {
load_total++;
}
// run the callback if the last script has loaded
if (load_counter === load_total) {
cb();
}
};
g_on_loaded_cb = checkForLoadDone;
// Load the formats
formats.forEach(function(archive_format) {
// Skip this format if it is already loaded
if (_loaded_archive_formats.indexOf(archive_format) !== -1) {
return;
}
// Load the archive format
switch (archive_format) {
case 'rar':
unrarMemoryFileLocation = path + 'libunrar.js.mem';
loadScript(path + 'libunrar.js', checkForLoadDone);
_loaded_archive_formats.push(archive_format);
break;
case 'zip':
loadScript(path + 'jszip.js', checkForLoadDone);
_loaded_archive_formats.push(archive_format);
break;
case 'tar':
loadScript(path + 'libuntar.js', checkForLoadDone);
_loaded_archive_formats.push(archive_format);
break;
default:
throw new Error("Unknown archive format '" + archive_format + "'.");
}
});
}
function archiveOpenFile(array_buffer, cb) {
let file_name = "Hugo"; //file.name;
let password = null;
try {
let archive = archiveOpenArrayBuffer(file_name, password, array_buffer);
cb(archive, null);
} catch(e) {
cb(null, e);
}
}
function archiveOpenArrayBuffer(file_name, password, array_buffer) {
// Get the archive type
let archive_type = null;
if (isRarFile(array_buffer)) {
archive_type = 'rar';
} else if(isZipFile(array_buffer)) {
archive_type = 'zip';
} else if(isTarFile(array_buffer)) {
archive_type = 'tar';
} else {
throw new Error("The archive type is unknown");
}
// Make sure the archive format is loaded
if (_loaded_archive_formats.indexOf(archive_type) === -1) {
throw new Error("The archive format '" + archive_type + "' is not loaded.");
}
// Get the entries
let handle = null;
let entries = [];
try {
switch (archive_type) {
case 'rar':
handle = _rarOpen(file_name, password, array_buffer);
entries = _rarGetEntries(handle);
break;
case 'zip':
handle = _zipOpen(file_name, password, array_buffer);
entries = _zipGetEntries(handle);
break;
case 'tar':
handle = _tarOpen(file_name, password, array_buffer);
entries = _tarGetEntries(handle);
break;
}
} catch(e) {
throw new Error("Failed to open '" + archive_type + "' archive.");
}
// Sort the entries by name
entries.sort(function(a, b) {
if(a.name < b.name) return -1;
if(a.name > b.name) return 1;
return 0;
});
// Return the archive object
return {
file_name: file_name,
archive_type: archive_type,
array_buffer: array_buffer,
entries: entries,
handle: handle
};
}
function archiveClose(archive) {
archive.file_name = null;
archive.archive_type = null;
archive.array_buffer = null;
archive.entries = null;
archive.handle = null;
}
function _rarOpen(file_name, password, array_buffer) {
// Create an array of rar files
let rar_files = [{
name: file_name,
size: array_buffer.byteLength,
type: '',
content: new Uint8Array(array_buffer)
}];
// Return rar handle
return {
file_name: file_name,
array_buffer: array_buffer,
password: password,
rar_files: rar_files
};
}
function _zipOpen(file_name, password, array_buffer) {
let zip = new JSZip(array_buffer);
// Return zip handle
return {
file_name: file_name,
array_buffer: array_buffer,
password: password,
zip: zip
};
}
function _tarOpen(file_name, password, array_buffer) {
// Return tar handle
return {
file_name: file_name,
array_buffer: array_buffer,
password: password
};
}
function _rarGetEntries(rar_handle) {
// Get the entries
let info = readRARFileNames(rar_handle.rar_files, rar_handle.password);
let entries = [];
Object.keys(info).forEach(function(i) {
let name = info[i].name;
let is_file = info[i].is_file;
if (is_file) {
entries.push({
name: name,
is_file: is_file, // info[i].is_file,
size_compressed: info[i].size_compressed,
size_uncompressed: info[i].size_uncompressed,
readData: function (cb) {
setTimeout(function () {
if (is_file) {
try {
readRARContent(rar_handle.rar_files, rar_handle.password, name, cb);
} catch (e) {
cb(null, e);
}
} else {
cb(null, null);
}
}, 0);
}
});
}
});
return entries;
}
function _zipGetEntries(zip_handle) {
let zip = zip_handle.zip;
// Get all the entries
let entries = [];
Object.keys(zip.files).forEach(function(i) {
let zip_entry = zip.files[i];
let name = zip_entry.name;
let is_file = ! zip_entry.dir;
let size_compressed = zip_entry._data ? zip_entry._data.compressedSize : 0;
let size_uncompressed = zip_entry._data ? zip_entry._data.uncompressedSize : 0;
if (is_file) {
entries.push({
name: name,
is_file: is_file,
size_compressed: size_compressed,
size_uncompressed: size_uncompressed,
readData: function (cb) {
setTimeout(function () {
if (is_file) {
let data = zip_entry.asArrayBuffer();
cb(data, null);
} else {
cb(null, null);
}
}, 0);
}
});
}
});
return entries;
}
function _tarGetEntries(tar_handle) {
let tar_entries = tarGetEntries(tar_handle.file_name, tar_handle.array_buffer);
// Get all the entries
let entries = [];
tar_entries.forEach(function(entry) {
let name = entry.name;
let is_file = entry.is_file;
let size = entry.size;
if (is_file) {
entries.push({
name: name,
is_file: is_file,
size_compressed: size,
size_uncompressed: size,
readData: function (cb) {
setTimeout(function () {
if (is_file) {
let data = tarGetEntryData(entry, tar_handle.array_buffer);
cb(data.buffer, null);
} else {
cb(null, null);
}
}, 0);
}
});
}
});
return entries;
}
function isRarFile(array_buffer) {
// The three styles of RAR headers
let rar_header1 = saneJoin([0x52, 0x45, 0x7E, 0x5E], ', '); // old
let rar_header2 = saneJoin([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00], ', '); // 1.5 to 4.0
let rar_header3 = saneJoin([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00], ', '); // 5.0
// Just return false if the file is smaller than the header
if (array_buffer.byteLength < 8) {
return false;
}
// Return true if the header matches one of the RAR headers
let header1 = saneJoin(new Uint8Array(array_buffer).slice(0, 4), ', ');
let header2 = saneJoin(new Uint8Array(array_buffer).slice(0, 7), ', ');
let header3 = saneJoin(new Uint8Array(array_buffer).slice(0, 8), ', ');
return (header1 === rar_header1 || header2 === rar_header2 || header3 === rar_header3);
}
function isZipFile(array_buffer) {
// The ZIP header
let zip_header = saneJoin([0x50, 0x4b, 0x03, 0x04], ', ');
// Just return false if the file is smaller than the header
if (array_buffer.byteLength < 4) {
return false;
}
// Return true if the header matches the ZIP header
let header = saneJoin(new Uint8Array(array_buffer).slice(0, 4), ', ');
return (header === zip_header);
}
function isTarFile(array_buffer) {
// The TAR header
let tar_header = saneJoin(['u', 's', 't', 'a', 'r'], ', ');
// Just return false if the file is smaller than the header size
if (array_buffer.byteLength < 512) {
return false;
}
// Return true if the header matches the TAR header
let header = saneJoin(saneMap(new Uint8Array(array_buffer).slice(257, 257 + 5), String.fromCharCode), ', ');
return (header === tar_header);
}
// Figure out if we are running in a Window or Web Worker
let scope = null;
if (typeof window === 'object') {
scope = window;
} else if (typeof importScripts === 'function') {
scope = self;
}
// Set exports
scope.loadArchiveFormats = loadArchiveFormats;
scope.archiveOpenFile = archiveOpenFile;
scope.archiveOpenArrayBuffer = archiveOpenArrayBuffer;
scope.archiveClose = archiveClose;
scope.isRarFile = isRarFile;
scope.isZipFile = isZipFile;
scope.isTarFile = isTarFile;
scope.saneJoin = saneJoin;
scope.saneMap = saneMap;
})();