Merge branch 'master' into Develop

# Conflicts:
#	cps/helper.py
#	cps/static/js/archive/unrar.js
#	cps/templates/readcbr.html
#	cps/templates/readpdf.html
#	cps/translations/de/LC_MESSAGES/messages.mo
#	cps/translations/de/LC_MESSAGES/messages.po
#	cps/translations/es/LC_MESSAGES/messages.mo
#	cps/translations/es/LC_MESSAGES/messages.po
#	cps/translations/fr/LC_MESSAGES/messages.mo
#	cps/translations/fr/LC_MESSAGES/messages.po
#	cps/translations/it/LC_MESSAGES/messages.mo
#	cps/translations/it/LC_MESSAGES/messages.po
#	cps/translations/ja/LC_MESSAGES/messages.mo
#	cps/translations/ja/LC_MESSAGES/messages.po
#	cps/translations/km/LC_MESSAGES/messages.mo
#	cps/translations/km/LC_MESSAGES/messages.po
#	cps/translations/nl/LC_MESSAGES/messages.mo
#	cps/translations/nl/LC_MESSAGES/messages.po
#	cps/translations/pl/LC_MESSAGES/messages.mo
#	cps/translations/pl/LC_MESSAGES/messages.po
#	cps/translations/ru/LC_MESSAGES/messages.mo
#	cps/translations/ru/LC_MESSAGES/messages.po
#	cps/translations/sv/LC_MESSAGES/messages.mo
#	cps/translations/sv/LC_MESSAGES/messages.po
#	cps/translations/uk/LC_MESSAGES/messages.mo
#	cps/translations/zh_Hans_CN/LC_MESSAGES/messages.mo
#	cps/translations/zh_Hans_CN/LC_MESSAGES/messages.po
#	cps/web.py
#	messages.pot
#	optional-requirements.txt
pull/932/head
Ozzieisaacs 6 years ago
commit 4fecce0a0d

@ -17,33 +17,50 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import zipfile
import tarfile
import os import os
import uploader from cps import uploader
from cps import app
from iso639 import languages as isoLanguages
def extractCover(tmp_file_name, original_file_extension): try:
cover_data = None from comicapi.comicarchive import ComicArchive, MetaDataStyle
if original_file_extension.upper() == '.CBZ': use_comic_meta = True
cf = zipfile.ZipFile(tmp_file_name) except ImportError as e:
for name in cf.namelist(): app.logger.warning('cannot import comicapi, extracting comic metadata will not work: %s', e)
ext = os.path.splitext(name) import zipfile
if len(ext) > 1: import tarfile
extension = ext[1].lower() use_comic_meta = False
if extension == '.jpg':
cover_data = cf.read(name)
break
elif original_file_extension.upper() == '.CBT':
cf = tarfile.TarFile(tmp_file_name)
for name in cf.getnames():
ext = os.path.splitext(name)
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg':
cover_data = cf.extractfile(name).read()
break
def extractCover(tmp_file_name, original_file_extension):
if use_comic_meta:
archive = ComicArchive(tmp_file_name)
cover_data = None
ext = os.path.splitext(archive.getPageName(0))
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg' or extension == '.jpeg':
cover_data = archive.getPage(0)
else:
if original_file_extension.upper() == '.CBZ':
cf = zipfile.ZipFile(tmp_file_name)
for name in cf.namelist():
ext = os.path.splitext(name)
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg':
cover_data = cf.read(name)
break
elif original_file_extension.upper() == '.CBT':
cf = tarfile.TarFile(tmp_file_name)
for name in cf.getnames():
ext = os.path.splitext(name)
if len(ext) > 1:
extension = ext[1].lower()
if extension == '.jpg':
cover_data = cf.extractfile(name).read()
break
prefix = os.path.dirname(tmp_file_name) prefix = os.path.dirname(tmp_file_name)
if cover_data: if cover_data:
tmp_cover_name = prefix + '/cover' + extension tmp_cover_name = prefix + '/cover' + extension
@ -56,15 +73,46 @@ def extractCover(tmp_file_name, original_file_extension):
def get_comic_info(tmp_file_path, original_file_name, original_file_extension): def get_comic_info(tmp_file_path, original_file_name, original_file_extension):
if use_comic_meta:
archive = ComicArchive(tmp_file_path)
if archive.seemsToBeAComicArchive():
if archive.hasMetadata(MetaDataStyle.CIX):
style = MetaDataStyle.CIX
elif archive.hasMetadata(MetaDataStyle.CBI):
style = MetaDataStyle.CBI
else:
style = None
coverfile = extractCover(tmp_file_path, original_file_extension) if style is not None:
loadedMetadata = archive.readMetadata(style)
lang = loadedMetadata.language
if len(lang) == 2:
loadedMetadata.language = isoLanguages.get(part1=lang).name
elif len(lang) == 3:
loadedMetadata.language = isoLanguages.get(part3=lang).name
else:
loadedMetadata.language = ""
return uploader.BookMeta(
file_path=tmp_file_path,
extension=original_file_extension,
title=loadedMetadata.title or original_file_name,
author=" & ".join([credit["person"] for credit in loadedMetadata.credits if credit["role"] == "Writer"]) or u"Unknown",
cover=extractCover(tmp_file_path, original_file_extension),
description=loadedMetadata.comments or "",
tags="",
series=loadedMetadata.series or "",
series_id=loadedMetadata.issue or "",
languages=loadedMetadata.language)
else:
return uploader.BookMeta( return uploader.BookMeta(
file_path=tmp_file_path, file_path=tmp_file_path,
extension=original_file_extension, extension=original_file_extension,
title=original_file_name, title=original_file_name,
author=u"Unknown", author=u"Unknown",
cover=coverfile, cover=extractCover(tmp_file_path, original_file_extension),
description="", description="",
tags="", tags="",
series="", series="",

@ -148,7 +148,7 @@ def send_registration_mail(e_mail, user_name, default_password, resend=False):
text += "Sincerely\r\n\r\n" text += "Sincerely\r\n\r\n"
text += "Your Calibre-Web team" text += "Your Calibre-Web team"
global_WorkerThread.add_email(_(u'Get Started with Calibre-Web'),None, None, ub.get_mail_settings(), global_WorkerThread.add_email(_(u'Get Started with Calibre-Web'),None, None, ub.get_mail_settings(),
e_mail, user_name, _(u"Registration e-mail for user: %(name)s", name=user_name), text) e_mail, None, _(u"Registration e-mail for user: %(name)s", name=user_name), text)
return return
@ -196,7 +196,7 @@ def check_send_to_kindle(entry):
# Check if a reader is existing for any of the book formats, if not, return empty list, otherwise return # Check if a reader is existing for any of the book formats, if not, return empty list, otherwise return
# list with supported formats # list with supported formats
def check_read_formats(entry): def check_read_formats(entry):
EXTENSIONS_READER = {'TXT', 'PDF', 'EPUB', 'ZIP', 'CBZ', 'TAR', 'CBT', 'RAR', 'CBR'} EXTENSIONS_READER = {'TXT', 'PDF', 'EPUB', 'CBZ', 'CBT', 'CBR'}
bookformats = list() bookformats = list()
if len(entry.data): if len(entry.data):
for ele in iter(entry.data): for ele in iter(entry.data):
@ -596,14 +596,11 @@ def json_serial(obj):
return obj.isoformat() return obj.isoformat()
raise TypeError ("Type %s not serializable" % type(obj)) raise TypeError ("Type %s not serializable" % type(obj))
# helper function to apply localize status information in tasklist entries
def render_task_status(tasklist): def render_task_status(tasklist):
#helper function to apply localize status information in tasklist entries
renderedtasklist=list() renderedtasklist=list()
# task2 = task
for task in tasklist: for task in tasklist:
if task['user'] == current_user.nickname or current_user.role_admin(): if task['user'] == current_user.nickname or current_user.role_admin():
# task2 = copy.deepcopy(task) # = task
if task['formStarttime']: if task['formStarttime']:
task['starttime'] = format_datetime(task['formStarttime'], format='short', locale=get_locale()) task['starttime'] = format_datetime(task['formStarttime'], format='short', locale=get_locale())
# task2['formStarttime'] = "" # task2['formStarttime'] = ""

@ -73,10 +73,13 @@ def mimetype_filter(val):
@jinjia.app_template_filter('formatdate') @jinjia.app_template_filter('formatdate')
def formatdate_filter(val): def formatdate_filter(val):
conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', val) try:
formatdate = datetime.datetime.strptime(conformed_timestamp[:15], "%Y%m%d %H%M%S") conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', val)
return format_date(formatdate, format='medium', locale=get_locale()) formatdate = datetime.datetime.strptime(conformed_timestamp[:15], "%Y%m%d %H%M%S")
return format_date(formatdate, format='medium', locale=get_locale())
except AttributeError as e:
app.logger.error('Babel error: %s, Current user locale: %s, Current User: %s' % (e, current_user.locale, current_user.nickname))
return formatdate
@jinjia.app_template_filter('formatdateinput') @jinjia.app_template_filter('formatdateinput')
def format_date_input(val): def format_date_input(val):

File diff suppressed because one or more lines are too long

@ -152,11 +152,11 @@ body {
max-width: 70%; max-width: 70%;
} }
#prev { #left {
left: 40px; left: 40px;
} }
#next { #right {
right: 40px; right: 40px;
} }

@ -17,7 +17,7 @@ bitjs.archive = bitjs.archive || {};
// =========================================================================== // ===========================================================================
// Stolen from Closure because it's the best way to do Java-like inheritance. // Stolen from Closure because it's the best way to do Java-like inheritance.
bitjs.base = function(me, optMethodName, varArgs) { bitjs.base = function(me, opt_methodName, var_args) {
var caller = arguments.callee.caller; var caller = arguments.callee.caller;
if (caller.superClass_) { if (caller.superClass_) {
// This is a constructor. Call the superclass constructor. // This is a constructor. Call the superclass constructor.
@ -27,12 +27,11 @@ bitjs.archive = bitjs.archive || {};
var args = Array.prototype.slice.call(arguments, 2); var args = Array.prototype.slice.call(arguments, 2);
var foundCaller = false; var foundCaller = false;
for (var ctor = me.constructor; for (var ctor = me.constructor; ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) { if (ctor.prototype[opt_methodName] === caller) {
if (ctor.prototype[optMethodName] === caller) {
foundCaller = true; foundCaller = true;
} else if (foundCaller) { } else if (foundCaller) {
return ctor.prototype[optMethodName].apply(me, args); return ctor.prototype[opt_methodName].apply(me, args);
} }
} }
@ -40,8 +39,8 @@ bitjs.archive = bitjs.archive || {};
// then one of two things happened: // then one of two things happened:
// 1) The caller is an instance method. // 1) The caller is an instance method.
// 2) This method was not called by the right caller. // 2) This method was not called by the right caller.
if (me[optMethodName] === caller) { if (me[opt_methodName] === caller) {
return me.constructor.prototype[optMethodName].apply(me, args); return me.constructor.prototype[opt_methodName].apply(me, args);
} else { } else {
throw Error( throw Error(
"goog.base called from a method of one name " + "goog.base called from a method of one name " +
@ -50,10 +49,10 @@ bitjs.archive = bitjs.archive || {};
}; };
bitjs.inherits = function(childCtor, parentCtor) { bitjs.inherits = function(childCtor, parentCtor) {
/** @constructor */ /** @constructor */
function TempCtor() {} function tempCtor() {};
TempCtor.prototype = parentCtor.prototype; tempCtor.prototype = parentCtor.prototype;
childCtor.superClass_ = parentCtor.prototype; childCtor.superClass_ = parentCtor.prototype;
childCtor.prototype = new TempCtor(); childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor; childCtor.prototype.constructor = childCtor;
}; };
// =========================================================================== // ===========================================================================
@ -66,10 +65,10 @@ bitjs.archive = bitjs.archive || {};
*/ */
bitjs.archive.UnarchiveEvent = function(type) { bitjs.archive.UnarchiveEvent = function(type) {
/** /**
* The event type. * The event type.
* *
* @type {string} * @type {string}
*/ */
this.type = type; this.type = type;
}; };
@ -94,10 +93,10 @@ bitjs.archive = bitjs.archive || {};
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO); bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO);
/** /**
* The information message. * The information message.
* *
* @type {string} * @type {string}
*/ */
this.msg = msg; this.msg = msg;
}; };
bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent); bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent);
@ -111,10 +110,10 @@ bitjs.archive = bitjs.archive || {};
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR); bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR);
/** /**
* The information message. * The information message.
* *
* @type {string} * @type {string}
*/ */
this.msg = msg; this.msg = msg;
}; };
bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent); bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent);
@ -178,8 +177,8 @@ bitjs.archive = bitjs.archive || {};
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT); bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT);
/** /**
* @type {UnarchivedFile} * @type {UnarchivedFile}
*/ */
this.unarchivedFile = unarchivedFile; this.unarchivedFile = unarchivedFile;
}; };
bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent); bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent);
@ -189,28 +188,28 @@ bitjs.archive = bitjs.archive || {};
* Base class for all Unarchivers. * Base class for all Unarchivers.
* *
* @param {ArrayBuffer} arrayBuffer The Array Buffer. * @param {ArrayBuffer} arrayBuffer The Array Buffer.
* @param {string} optPathToBitJS Optional string for where the BitJS files are located. * @param {string} opt_pathToBitJS Optional string for where the BitJS files are located.
* @constructor * @constructor
*/ */
bitjs.archive.Unarchiver = function(arrayBuffer, optPathToBitJS) { bitjs.archive.Unarchiver = function(arrayBuffer, opt_pathToBitJS) {
/** /**
* The ArrayBuffer object. * The ArrayBuffer object.
* @type {ArrayBuffer} * @type {ArrayBuffer}
* @protected * @protected
*/ */
this.ab = arrayBuffer; this.ab = arrayBuffer;
/** /**
* The path to the BitJS files. * The path to the BitJS files.
* @type {string} * @type {string}
* @private * @private
*/ */
this.pathToBitJS_ = optPathToBitJS || ""; this.pathToBitJS_ = opt_pathToBitJS || "/";
/** /**
* A map from event type to an array of listeners. * A map from event type to an array of listeners.
* @type {Map.<string, Array>} * @type {Map.<string, Array>}
*/ */
this.listeners_ = {}; this.listeners_ = {};
for (var type in bitjs.archive.UnarchiveEvent.Type) { for (var type in bitjs.archive.UnarchiveEvent.Type) {
this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = []; this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = [];
@ -282,7 +281,7 @@ bitjs.archive = bitjs.archive || {};
/** /**
* Starts the unarchive in a separate Web Worker thread and returns immediately. * Starts the unarchive in a separate Web Worker thread and returns immediately.
*/ */
bitjs.archive.Unarchiver.prototype.start = function() { bitjs.archive.Unarchiver.prototype.start = function() {
var me = this; var me = this;
var scriptFileName = this.pathToBitJS_ + this.getScriptFileName(); var scriptFileName = this.pathToBitJS_ + this.getScriptFileName();
@ -320,8 +319,8 @@ bitjs.archive = bitjs.archive || {};
* @extends {bitjs.archive.Unarchiver} * @extends {bitjs.archive.Unarchiver}
* @constructor * @constructor
*/ */
bitjs.archive.Unzipper = function(arrayBuffer, optPathToBitJS) { bitjs.archive.Unzipper = function(arrayBuffer, opt_pathToBitJS) {
bitjs.base(this, arrayBuffer, optPathToBitJS); bitjs.base(this, arrayBuffer, opt_pathToBitJS);
}; };
bitjs.inherits(bitjs.archive.Unzipper, bitjs.archive.Unarchiver); bitjs.inherits(bitjs.archive.Unzipper, bitjs.archive.Unarchiver);
bitjs.archive.Unzipper.prototype.getScriptFileName = function() { bitjs.archive.Unzipper.prototype.getScriptFileName = function() {
@ -333,8 +332,8 @@ bitjs.archive = bitjs.archive || {};
* @extends {bitjs.archive.Unarchiver} * @extends {bitjs.archive.Unarchiver}
* @constructor * @constructor
*/ */
bitjs.archive.Unrarrer = function(arrayBuffer, optPathToBitJS) { bitjs.archive.Unrarrer = function(arrayBuffer, opt_pathToBitJS) {
bitjs.base(this, arrayBuffer, optPathToBitJS); bitjs.base(this, arrayBuffer, opt_pathToBitJS);
}; };
bitjs.inherits(bitjs.archive.Unrarrer, bitjs.archive.Unarchiver); bitjs.inherits(bitjs.archive.Unrarrer, bitjs.archive.Unarchiver);
bitjs.archive.Unrarrer.prototype.getScriptFileName = function() { bitjs.archive.Unrarrer.prototype.getScriptFileName = function() {
@ -346,12 +345,35 @@ bitjs.archive = bitjs.archive || {};
* @extends {bitjs.archive.Unarchiver} * @extends {bitjs.archive.Unarchiver}
* @constructor * @constructor
*/ */
bitjs.archive.Untarrer = function(arrayBuffer, optPathToBitJS) { bitjs.archive.Untarrer = function(arrayBuffer, opt_pathToBitJS) {
bitjs.base(this, arrayBuffer, optPathToBitJS); bitjs.base(this, arrayBuffer, opt_pathToBitJS);
}; };
bitjs.inherits(bitjs.archive.Untarrer, bitjs.archive.Unarchiver); bitjs.inherits(bitjs.archive.Untarrer, bitjs.archive.Unarchiver);
bitjs.archive.Untarrer.prototype.getScriptFileName = function() { bitjs.archive.Untarrer.prototype.getScriptFileName = function() {
return "untar.js"; return "untar.js";
}; };
/**
* Factory method that creates an unarchiver based on the byte signature found
* in the arrayBuffer.
* @param {ArrayBuffer} ab
* @param {string=} opt_pathToBitJS Path to the unarchiver script files.
* @return {bitjs.archive.Unarchiver}
*/
bitjs.archive.GetUnarchiver = function(ab, opt_pathToBitJS) {
var unarchiver = null;
var pathToBitJS = opt_pathToBitJS || '';
var h = new Uint8Array(ab, 0, 10);
if (h[0] == 0x52 && h[1] == 0x61 && h[2] == 0x72 && h[3] == 0x21) { // Rar!
unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS);
} else if (h[0] == 80 && h[1] == 75) { // PK (Zip)
unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS);
} else { // Try with tar
console.log('geter');
unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS);
}
return unarchiver;
};
})(); })();

@ -0,0 +1,858 @@
/**
* rarvm.js
*
* Licensed under the MIT License
*
* Copyright(c) 2017 Google Inc.
*/
/**
* CRC Implementation.
*/
var CRCTab = new Array(256).fill(0);
function InitCRC() {
for (var i = 0; i < 256; ++i) {
var c = i;
for (var j = 0; j < 8; ++j) {
// Read http://stackoverflow.com/questions/6798111/bitwise-operations-on-32-bit-unsigned-ints
// for the bitwise operator issue (JS interprets operands as 32-bit signed
// integers and we need to deal with unsigned ones here).
c = ((c & 1) ? ((c >>> 1) ^ 0xEDB88320) : (c >>> 1)) >>> 0;
}
CRCTab[i] = c;
}
}
/**
* @param {number} startCRC
* @param {Uint8Array} arr
* @return {number}
*/
function CRC(startCRC, arr) {
if (CRCTab[1] == 0) {
InitCRC();
}
/*
#if defined(LITTLE_ENDIAN) && defined(PRESENT_INT32) && defined(ALLOW_NOT_ALIGNED_INT)
while (Size>0 && ((long)Data & 7))
{
StartCRC=CRCTab[(byte)(StartCRC^Data[0])]^(StartCRC>>8);
Size--;
Data++;
}
while (Size>=8)
{
StartCRC^=*(uint32 *)Data;
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC^=*(uint32 *)(Data+4);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8);
Data+=8;
Size-=8;
}
#endif
*/
for (var i = 0; i < arr.length; ++i) {
var byte = ((startCRC ^ arr[i]) >>> 0) & 0xff;
startCRC = (CRCTab[byte] ^ (startCRC >>> 8)) >>> 0;
}
return startCRC;
}
// ============================================================================================== //
/**
* RarVM Implementation.
*/
var VM_MEMSIZE = 0x40000;
var VM_MEMMASK = (VM_MEMSIZE - 1);
var VM_GLOBALMEMADDR = 0x3C000;
var VM_GLOBALMEMSIZE = 0x2000;
var VM_FIXEDGLOBALSIZE = 64;
var MAXWINSIZE = 0x400000;
var MAXWINMASK = (MAXWINSIZE - 1);
/**
*/
var VM_Commands = {
VM_MOV: 0,
VM_CMP: 1,
VM_ADD: 2,
VM_SUB: 3,
VM_JZ: 4,
VM_JNZ: 5,
VM_INC: 6,
VM_DEC: 7,
VM_JMP: 8,
VM_XOR: 9,
VM_AND: 10,
VM_OR: 11,
VM_TEST: 12,
VM_JS: 13,
VM_JNS: 14,
VM_JB: 15,
VM_JBE: 16,
VM_JA: 17,
VM_JAE: 18,
VM_PUSH: 19,
VM_POP: 20,
VM_CALL: 21,
VM_RET: 22,
VM_NOT: 23,
VM_SHL: 24,
VM_SHR: 25,
VM_SAR: 26,
VM_NEG: 27,
VM_PUSHA: 28,
VM_POPA: 29,
VM_PUSHF: 30,
VM_POPF: 31,
VM_MOVZX: 32,
VM_MOVSX: 33,
VM_XCHG: 34,
VM_MUL: 35,
VM_DIV: 36,
VM_ADC: 37,
VM_SBB: 38,
VM_PRINT: 39,
/*
#ifdef VM_OPTIMIZE
VM_MOVB, VM_MOVD, VM_CMPB, VM_CMPD,
VM_ADDB, VM_ADDD, VM_SUBB, VM_SUBD, VM_INCB, VM_INCD, VM_DECB, VM_DECD,
VM_NEGB, VM_NEGD,
#endif
*/
// TODO: This enum value would be much larger if VM_OPTIMIZE.
VM_STANDARD: 40,
};
/**
*/
var VM_StandardFilters = {
VMSF_NONE: 0,
VMSF_E8: 1,
VMSF_E8E9: 2,
VMSF_ITANIUM: 3,
VMSF_RGB: 4,
VMSF_AUDIO: 5,
VMSF_DELTA: 6,
VMSF_UPCASE: 7,
};
/**
*/
var VM_Flags = {
VM_FC: 1,
VM_FZ: 2,
VM_FS: 0x80000000,
};
/**
*/
var VM_OpType = {
VM_OPREG: 0,
VM_OPINT: 1,
VM_OPREGMEM: 2,
VM_OPNONE: 3,
};
/**
* Finds the key that maps to a given value in an object. This function is useful in debugging
* variables that use the above enums.
* @param {Object} obj
* @param {number} val
* @return {string} The key/enum value as a string.
*/
function findKeyForValue(obj, val) {
for (var key in obj) {
if (obj[key] === val) {
return key;
}
}
return null;
}
function getDebugString(obj, val) {
var s = 'Unknown.';
if (obj === VM_Commands) {
s = 'VM_Commands.';
} else if (obj === VM_StandardFilters) {
s = 'VM_StandardFilters.';
} else if (obj === VM_Flags) {
s = 'VM_OpType.';
} else if (obj === VM_OpType) {
s = 'VM_OpType.';
}
return s + findKeyForValue(obj, val);
}
/**
* @struct
* @constructor
*/
var VM_PreparedOperand = function() {
/** @type {VM_OpType} */
this.Type;
/** @type {number} */
this.Data = 0;
/** @type {number} */
this.Base = 0;
// TODO: In C++ this is a uint*
/** @type {Array<number>} */
this.Addr = null;
};
/** @return {string} */
VM_PreparedOperand.prototype.toString = function() {
if (this.Type === null) {
return 'Error: Type was null in VM_PreparedOperand';
}
return '{ ' +
'Type: ' + getDebugString(VM_OpType, this.Type) +
', Data: ' + this.Data +
', Base: ' + this.Base +
' }';
};
/**
* @struct
* @constructor
*/
var VM_PreparedCommand = function() {
/** @type {VM_Commands} */
this.OpCode;
/** @type {boolean} */
this.ByteMode = false;
/** @type {VM_PreparedOperand} */
this.Op1 = new VM_PreparedOperand();
/** @type {VM_PreparedOperand} */
this.Op2 = new VM_PreparedOperand();
};
/** @return {string} */
VM_PreparedCommand.prototype.toString = function(indent) {
if (this.OpCode === null) {
return 'Error: OpCode was null in VM_PreparedCommand';
}
indent = indent || '';
return indent + '{\n' +
indent + ' OpCode: ' + getDebugString(VM_Commands, this.OpCode) + ',\n' +
indent + ' ByteMode: ' + this.ByteMode + ',\n' +
indent + ' Op1: ' + this.Op1.toString() + ',\n' +
indent + ' Op2: ' + this.Op2.toString() + ',\n' +
indent + '}';
};
/**
* @struct
* @constructor
*/
var VM_PreparedProgram = function() {
/** @type {Array<VM_PreparedCommand>} */
this.Cmd = [];
/** @type {Array<VM_PreparedCommand>} */
this.AltCmd = null;
/** @type {Uint8Array} */
this.GlobalData = new Uint8Array();
/** @type {Uint8Array} */
this.StaticData = new Uint8Array(); // static data contained in DB operators
/** @type {Uint32Array} */
this.InitR = new Uint32Array(7);
/**
* A pointer to bytes that have been filtered by a program.
* @type {Uint8Array}
*/
this.FilteredData = null;
};
/** @return {string} */
VM_PreparedProgram.prototype.toString = function() {
var s = '{\n Cmd: [\n';
for (var i = 0; i < this.Cmd.length; ++i) {
s += this.Cmd[i].toString(' ') + ',\n';
}
s += '],\n';
// TODO: Dump GlobalData, StaticData, InitR?
s += ' }\n';
return s;
};
/**
* @struct
* @constructor
*/
var UnpackFilter = function() {
/** @type {number} */
this.BlockStart = 0;
/** @type {number} */
this.BlockLength = 0;
/** @type {number} */
this.ExecCount = 0;
/** @type {boolean} */
this.NextWindow = false;
// position of parent filter in Filters array used as prototype for filter
// in PrgStack array. Not defined for filters in Filters array.
/** @type {number} */
this.ParentFilter = null;
/** @type {VM_PreparedProgram} */
this.Prg = new VM_PreparedProgram();
};
var VMCF_OP0 = 0;
var VMCF_OP1 = 1;
var VMCF_OP2 = 2;
var VMCF_OPMASK = 3;
var VMCF_BYTEMODE = 4;
var VMCF_JUMP = 8;
var VMCF_PROC = 16;
var VMCF_USEFLAGS = 32;
var VMCF_CHFLAGS = 64;
var VM_CmdFlags = [
/* VM_MOV */
VMCF_OP2 | VMCF_BYTEMODE,
/* VM_CMP */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_ADD */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_SUB */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_JZ */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JNZ */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_INC */
VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_DEC */
VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_JMP */
VMCF_OP1 | VMCF_JUMP,
/* VM_XOR */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_AND */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_OR */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_TEST */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_JS */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JNS */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JB */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JBE */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JA */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_JAE */
VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS,
/* VM_PUSH */
VMCF_OP1,
/* VM_POP */
VMCF_OP1,
/* VM_CALL */
VMCF_OP1 | VMCF_PROC,
/* VM_RET */
VMCF_OP0 | VMCF_PROC,
/* VM_NOT */
VMCF_OP1 | VMCF_BYTEMODE,
/* VM_SHL */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_SHR */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_SAR */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_NEG */
VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS,
/* VM_PUSHA */
VMCF_OP0,
/* VM_POPA */
VMCF_OP0,
/* VM_PUSHF */
VMCF_OP0 | VMCF_USEFLAGS,
/* VM_POPF */
VMCF_OP0 | VMCF_CHFLAGS,
/* VM_MOVZX */
VMCF_OP2,
/* VM_MOVSX */
VMCF_OP2,
/* VM_XCHG */
VMCF_OP2 | VMCF_BYTEMODE,
/* VM_MUL */
VMCF_OP2 | VMCF_BYTEMODE,
/* VM_DIV */
VMCF_OP2 | VMCF_BYTEMODE,
/* VM_ADC */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS,
/* VM_SBB */
VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS,
/* VM_PRINT */
VMCF_OP0,
];
/**
* @param {number} length
* @param {number} crc
* @param {VM_StandardFilters} type
* @struct
* @constructor
*/
var StandardFilterSignature = function(length, crc, type) {
/** @type {number} */
this.Length = length;
/** @type {number} */
this.CRC = crc;
/** @type {VM_StandardFilters} */
this.Type = type;
};
/**
* @type {Array<StandardFilterSignature>}
*/
var StdList = [
new StandardFilterSignature(53, 0xad576887, VM_StandardFilters.VMSF_E8),
new StandardFilterSignature(57, 0x3cd7e57e, VM_StandardFilters.VMSF_E8E9),
new StandardFilterSignature(120, 0x3769893f, VM_StandardFilters.VMSF_ITANIUM),
new StandardFilterSignature(29, 0x0e06077d, VM_StandardFilters.VMSF_DELTA),
new StandardFilterSignature(149, 0x1c2c5dc8, VM_StandardFilters.VMSF_RGB),
new StandardFilterSignature(216, 0xbc85e701, VM_StandardFilters.VMSF_AUDIO),
new StandardFilterSignature(40, 0x46b9c560, VM_StandardFilters.VMSF_UPCASE),
];
/**
* @constructor
*/
var RarVM = function() {
/** @private {Uint8Array} */
this.mem_ = null;
/** @private {Uint32Array<number>} */
this.R_ = new Uint32Array(8);
/** @private {number} */
this.flags_ = 0;
};
/**
* Initializes the memory of the VM.
*/
RarVM.prototype.init = function() {
if (!this.mem_) {
this.mem_ = new Uint8Array(VM_MEMSIZE);
}
};
/**
* @param {Uint8Array} code
* @return {VM_StandardFilters}
*/
RarVM.prototype.isStandardFilter = function(code) {
var codeCRC = (CRC(0xffffffff, code, code.length) ^ 0xffffffff) >>> 0;
for (var i = 0; i < StdList.length; ++i) {
if (StdList[i].CRC == codeCRC && StdList[i].Length == code.length)
return StdList[i].Type;
}
return VM_StandardFilters.VMSF_NONE;
};
/**
* @param {VM_PreparedOperand} op
* @param {boolean} byteMode
* @param {bitjs.io.BitStream} bstream A rtl bit stream.
*/
RarVM.prototype.decodeArg = function(op, byteMode, bstream) {
var data = bstream.peekBits(16);
if (data & 0x8000) {
op.Type = VM_OpType.VM_OPREG; // Operand is register (R[0]..R[7])
bstream.readBits(1); // 1 flag bit and...
op.Data = bstream.readBits(3); // ... 3 register number bits
op.Addr = [this.R_[op.Data]] // TODO &R[Op.Data] // Register address
} else {
if ((data & 0xc000) == 0) {
op.Type = VM_OpType.VM_OPINT; // Operand is integer
bstream.readBits(2); // 2 flag bits
if (byteMode) {
op.Data = bstream.readBits(8); // Byte integer.
} else {
op.Data = RarVM.readData(bstream); // 32 bit integer.
}
} else {
// Operand is data addressed by register data, base address or both.
op.Type = VM_OpType.VM_OPREGMEM;
if ((data & 0x2000) == 0) {
bstream.readBits(3); // 3 flag bits
// Base address is zero, just use the address from register.
op.Data = bstream.readBits(3); // (Data>>10)&7
op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data]
op.Base = 0;
} else {
bstream.readBits(4); // 4 flag bits
if ((data & 0x1000) == 0) {
// Use both register and base address.
op.Data = bstream.readBits(3);
op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data]
} else {
// Use base address only. Access memory by fixed address.
op.Data = 0;
}
op.Base = RarVM.readData(bstream); // Read base address.
}
}
}
};
/**
* @param {VM_PreparedProgram} prg
*/
RarVM.prototype.execute = function(prg) {
this.R_.set(prg.InitR);
var globalSize = Math.min(prg.GlobalData.length, VM_GLOBALMEMSIZE);
if (globalSize) {
this.mem_.set(prg.GlobalData.subarray(0, globalSize), VM_GLOBALMEMADDR);
}
var staticSize = Math.min(prg.StaticData.length, VM_GLOBALMEMSIZE - globalSize);
if (staticSize) {
this.mem_.set(prg.StaticData.subarray(0, staticSize), VM_GLOBALMEMADDR + globalSize);
}
this.R_[7] = VM_MEMSIZE;
this.flags_ = 0;
var preparedCodes = prg.AltCmd ? prg.AltCmd : prg.Cmd;
if (prg.Cmd.length > 0 && !this.executeCode(preparedCodes)) {
// Invalid VM program. Let's replace it with 'return' command.
preparedCode.OpCode = VM_Commands.VM_RET;
}
var dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR);
var newBlockPos = dataView.getUint32(0x20, true /* little endian */ ) & VM_MEMMASK;
var newBlockSize = dataView.getUint32(0x1c, true /* little endian */ ) & VM_MEMMASK;
if (newBlockPos + newBlockSize >= VM_MEMSIZE) {
newBlockPos = newBlockSize = 0;
}
prg.FilteredData = this.mem_.subarray(newBlockPos, newBlockPos + newBlockSize);
prg.GlobalData = new Uint8Array(0);
var dataSize = Math.min(dataView.getUint32(0x30),
(VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE));
if (dataSize != 0) {
var len = dataSize + VM_FIXEDGLOBALSIZE;
prg.GlobalData = new Uint8Array(len);
prg.GlobalData.set(mem.subarray(VM_GLOBALMEMADDR, VM_GLOBALMEMADDR + len));
}
};
/**
* @param {Array<VM_PreparedCommand>} preparedCodes
* @return {boolean}
*/
RarVM.prototype.executeCode = function(preparedCodes) {
var codeIndex = 0;
var cmd = preparedCodes[codeIndex];
// TODO: Why is this an infinite loop instead of just returning
// when a VM_RET is hit?
while (1) {
switch (cmd.OpCode) {
case VM_Commands.VM_RET:
if (this.R_[7] >= VM_MEMSIZE) {
return true;
}
//SET_IP(GET_VALUE(false,(uint *)&Mem[R[7] & VM_MEMMASK]));
this.R_[7] += 4;
continue;
case VM_Commands.VM_STANDARD:
this.executeStandardFilter(cmd.Op1.Data);
break;
default:
console.error('RarVM OpCode not supported: ' + getDebugString(VM_Commands, cmd.OpCode));
break;
} // switch (cmd.OpCode)
codeIndex++;
cmd = preparedCodes[codeIndex];
}
};
/**
* @param {number} filterType
*/
RarVM.prototype.executeStandardFilter = function(filterType) {
switch (filterType) {
case VM_StandardFilters.VMSF_DELTA:
var dataSize = this.R_[4];
var channels = this.R_[0];
var srcPos = 0;
var border = dataSize * 2;
//SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize);
var dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR);
dataView.setUint32(0x20, dataSize, true /* little endian */ );
if (dataSize >= VM_GLOBALMEMADDR / 2) {
break;
}
// Bytes from same channels are grouped to continual data blocks,
// so we need to place them back to their interleaving positions.
for (var curChannel = 0; curChannel < channels; ++curChannel) {
var prevByte = 0;
for (var destPos = dataSize + curChannel; destPos < border; destPos += channels) {
prevByte = (prevByte - this.mem_[srcPos++]) & 0xff;
this.mem_[destPos] = prevByte;
}
}
break;
default:
console.error('RarVM Standard Filter not supported: ' + getDebugString(VM_StandardFilters, filterType));
break;
}
};
/**
* @param {Uint8Array} code
* @param {VM_PreparedProgram} prg
*/
RarVM.prototype.prepare = function(code, prg) {
var codeSize = code.length;
//InitBitInput();
//memcpy(InBuf,Code,Min(CodeSize,BitInput::MAX_SIZE));
var bstream = new bitjs.io.BitStream(code.buffer, true /* rtl */ );
// Calculate the single byte XOR checksum to check validity of VM code.
var xorSum = 0;
for (var i = 1; i < codeSize; ++i) {
xorSum ^= code[i];
}
bstream.readBits(8);
prg.Cmd = []; // TODO: Is this right? I don't see it being done in rarvm.cpp.
// VM code is valid if equal.
if (xorSum == code[0]) {
var filterType = this.isStandardFilter(code);
if (filterType != VM_StandardFilters.VMSF_NONE) {
// VM code is found among standard filters.
var curCmd = new VM_PreparedCommand();
prg.Cmd.push(curCmd);
curCmd.OpCode = VM_Commands.VM_STANDARD;
curCmd.Op1.Data = filterType;
// TODO: Addr=&CurCmd->Op1.Data
curCmd.Op1.Addr = [curCmd.Op1.Data];
curCmd.Op2.Addr = [null]; // &CurCmd->Op2.Data;
curCmd.Op1.Type = VM_OpType.VM_OPNONE;
curCmd.Op2.Type = VM_OpType.VM_OPNONE;
codeSize = 0;
}
var dataFlag = bstream.readBits(1);
// Read static data contained in DB operators. This data cannot be
// changed, it is a part of VM code, not a filter parameter.
if (dataFlag & 0x8000) {
var dataSize = RarVM.readData(bstream) + 1;
// TODO: This accesses the byte pointer of the bstream directly. Is that ok?
for (var i = 0; i < bstream.bytePtr < codeSize && i < dataSize; ++i) {
// Append a byte to the program's static data.
var newStaticData = new Uint8Array(prg.StaticData.length + 1);
newStaticData.set(prg.StaticData);
newStaticData[newStaticData.length - 1] = bstream.readBits(8);
prg.StaticData = newStaticData;
}
}
while (bstream.bytePtr < codeSize) {
var curCmd = new VM_PreparedCommand();
prg.Cmd.push(curCmd); // Prg->Cmd.Add(1)
var flag = bstream.peekBits(1);
if (!flag) { // (Data&0x8000)==0
curCmd.OpCode = bstream.readBits(4);
} else {
curCmd.OpCode = (bstream.readBits(6) - 24);
}
if (VM_CmdFlags[curCmd.OpCode] & VMCF_BYTEMODE) {
curCmd.ByteMode = (bstream.readBits(1) != 0);
} else {
curCmd.ByteMode = 0;
}
curCmd.Op1.Type = VM_OpType.VM_OPNONE;
curCmd.Op2.Type = VM_OpType.VM_OPNONE;
var opNum = (VM_CmdFlags[curCmd.OpCode] & VMCF_OPMASK);
curCmd.Op1.Addr = null;
curCmd.Op2.Addr = null;
if (opNum > 0) {
this.decodeArg(curCmd.Op1, curCmd.ByteMode, bstream); // reading the first operand
if (opNum == 2) {
this.decodeArg(curCmd.Op2, curCmd.ByteMode, bstream); // reading the second operand
} else {
if (curCmd.Op1.Type == VM_OpType.VM_OPINT && (VM_CmdFlags[curCmd.OpCode] & (VMCF_JUMP | VMCF_PROC))) {
// Calculating jump distance.
var distance = curCmd.Op1.Data;
if (distance >= 256) {
distance -= 256;
} else {
if (distance >= 136) {
distance -= 264;
} else {
if (distance >= 16) {
distance -= 8;
} else {
if (distance >= 8) {
distance -= 16;
}
}
}
distance += prg.Cmd.length;
}
curCmd.Op1.Data = distance;
}
}
} // if (OpNum>0)
} // while ((uint)InAddr<CodeSize)
} // if (XorSum==Code[0])
var curCmd = new VM_PreparedCommand();
prg.Cmd.push(curCmd);
curCmd.OpCode = VM_Commands.VM_RET;
// TODO: Addr=&CurCmd->Op1.Data
curCmd.Op1.Addr = [curCmd.Op1.Data];
curCmd.Op2.Addr = [curCmd.Op2.Data];
curCmd.Op1.Type = VM_OpType.VM_OPNONE;
curCmd.Op2.Type = VM_OpType.VM_OPNONE;
// If operand 'Addr' field has not been set by DecodeArg calls above,
// let's set it to point to operand 'Data' field. It is necessary for
// VM_OPINT type operands (usual integers) or maybe if something was
// not set properly for other operands. 'Addr' field is required
// for quicker addressing of operand data.
for (var i = 0; i < prg.Cmd.length; ++i) {
var cmd = prg.Cmd[i];
if (cmd.Op1.Addr == null) {
cmd.Op1.Addr = [cmd.Op1.Data];
}
if (cmd.Op2.Addr == null) {
cmd.Op2.Addr = [cmd.Op2.Data];
}
}
/*
#ifdef VM_OPTIMIZE
if (CodeSize!=0)
Optimize(Prg);
#endif
*/
};
/**
* @param {Uint8Array} arr The byte array to set a value in.
* @param {number} value The unsigned 32-bit value to set.
* @param {number} offset Offset into arr to start setting the value, defaults to 0.
*/
RarVM.prototype.setLowEndianValue = function(arr, value, offset) {
var i = offset || 0;
arr[i] = value & 0xff;
arr[i + 1] = (value >>> 8) & 0xff;
arr[i + 2] = (value >>> 16) & 0xff;
arr[i + 3] = (value >>> 24) & 0xff;
};
/**
* Sets a number of bytes of the VM memory at the given position from a
* source buffer of bytes.
* @param {number} pos The position in the VM memory to start writing to.
* @param {Uint8Array} buffer The source buffer of bytes.
* @param {number} dataSize The number of bytes to set.
*/
RarVM.prototype.setMemory = function(pos, buffer, dataSize) {
if (pos < VM_MEMSIZE) {
var numBytes = Math.min(dataSize, VM_MEMSIZE - pos);
for (var i = 0; i < numBytes; ++i) {
this.mem_[pos + i] = buffer[i];
}
}
};
/**
* Static function that reads in the next set of bits for the VM
* (might return 4, 8, 16 or 32 bits).
* @param {bitjs.io.BitStream} bstream A RTL bit stream.
* @return {number} The value of the bits read.
*/
RarVM.readData = function(bstream) {
// Read in the first 2 bits.
var flags = bstream.readBits(2);
switch (flags) { // Data&0xc000
// Return the next 4 bits.
case 0:
return bstream.readBits(4); // (Data>>10)&0xf
case 1: // 0x4000
// 0x3c00 => 0011 1100 0000 0000
if (bstream.peekBits(4) == 0) { // (Data&0x3c00)==0
// Skip the 4 zero bits.
bstream.readBits(4);
// Read in the next 8 and pad with 1s to 32 bits.
return (0xffffff00 | bstream.readBits(8)) >>> 0; // ((Data>>2)&0xff)
}
// Else, read in the next 8.
return bstream.readBits(8);
// Read in the next 16.
case 2: // 0x8000
var val = bstream.getBits();
bstream.readBits(16);
return val; //bstream.readBits(16);
// case 3
default:
return (bstream.readBits(16) << 16) | bstream.readBits(16);
}
};
// ============================================================================================== //

@ -1,6 +1,8 @@
/** /**
* unrar.js * unrar.js
* *
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc. * Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15 * Copyright(c) 2011 antimatter15
* *
@ -11,8 +13,10 @@
/* global bitjs, importScripts */ /* global bitjs, importScripts */
// This file expects to be invoked as a Worker (see onmessage below). // This file expects to be invoked as a Worker (see onmessage below).
importScripts("io.js"); importScripts('../io/bitstream.js');
importScripts("archive.js"); importScripts('../io/bytebuffer.js');
importScripts('archive.js');
importScripts('rarvm.js');
// Progress variables. // Progress variables.
var currentFilename = ""; var currentFilename = "";
@ -59,11 +63,15 @@ var MAIN_HEAD = 0x73,
// PROTECT_HEAD = 0x78, // PROTECT_HEAD = 0x78,
// SIGN_HEAD = 0x79, // SIGN_HEAD = 0x79,
// NEWSUB_HEAD = 0x7a, // NEWSUB_HEAD = 0x7a,
ENDARC_HEAD = 0x7b; ENDARC_HEAD = 0x7b;
// bstream is a bit stream // ============================================================================================== //
var RarVolumeHeader = function(bstream) {
/**
* @param {bitjs.io.BitStream} bstream
* @constructor
*/
var RarVolumeHeader = function(bstream) {
var headPos = bstream.bytePtr; var headPos = bstream.bytePtr;
// byte 1,2 // byte 1,2
info("Rar Volume Header @" + bstream.bytePtr); info("Rar Volume Header @" + bstream.bytePtr);
@ -197,15 +205,18 @@ var RarVolumeHeader = function(bstream) {
} }
while (headPos + this.headSize > bstream.bytePtr) bstream.readBits(1); while (headPos + this.headSize > bstream.bytePtr) {
bstream.readBits(1);
}
info("Found FILE_HEAD with packSize=" + this.packSize + ", unpackedSize= " + this.unpackedSize + ", hostOS=" + this.hostOS + ", unpVer=" + this.unpVer + ", method=" + this.method + ", filename=" + this.filename); // If Info line is commented in firefox fails if server on same computer than browser with error "expected expression, got default"
//info("Found FILE_HEAD with packSize=" + this.packSize + ", unpackedSize= " + this.unpackedSize + ", hostOS=" + this.hostOS + ", unpVer=" + this.unpVer + ", method=" + this.method + ", filename=" + this.filename);
break; break;
default: default:
info("Found a header of type 0x" + byteValueToHexString(this.headType)); info("Found a header of type 0x" + byteValueToHexString(this.headType));
// skip the rest of the header bytes (for now) // skip the rest of the header bytes (for now)
bstream.readBytes( this.headSize - 7 ); bstream.readBytes(this.headSize - 7);
break; break;
} }
}; };
@ -213,20 +224,22 @@ var RarVolumeHeader = function(bstream) {
//var BLOCK_LZ = 0; //var BLOCK_LZ = 0;
var rLDecode = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224], var rLDecode = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224],
rLBits = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5], rLBits = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
rDBitLengthCounts = [4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12], rDBitLengthCounts = [4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12],
rSDDecode = [0, 4, 8, 16, 32, 64, 128, 192], rSDDecode = [0, 4, 8, 16, 32, 64, 128, 192],
rSDBits = [2, 2, 3, 4, 5, 6, 6, 6]; rSDBits = [2, 2, 3, 4, 5, 6, 6, 6];
var rDDecode = [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, var rDDecode = [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32,
48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072,
4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304,
131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824, 131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824,
655360, 720896, 786432, 851968, 917504, 983040]; 655360, 720896, 786432, 851968, 917504, 983040
];
var rDBits = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, var rDBits = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5,
5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14,
15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]; 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16
];
var rLowDistRepCount = 16; var rLowDistRepCount = 16;
@ -266,15 +279,45 @@ var RD = { //rep decode
DecodeNum: new Array(rRC) DecodeNum: new Array(rRC)
}; };
/**
* @type {Array<bitjs.io.ByteBuffer>}
*/
var rOldBuffers = [];
/**
* The current buffer we are unpacking to.
* @type {bitjs.io.ByteBuffer}
*/
var rBuffer; var rBuffer;
// read in Huffman tables for RAR /**
* The buffer of the final bytes after filtering (only used in Unpack29).
* @type {bitjs.io.ByteBuffer}
*/
var wBuffer;
/**
* In unpack.cpp, UnpPtr keeps track of what bytes have been unpacked
* into the Window buffer and WrPtr keeps track of what bytes have been
* actually written to disk after the unpacking and optional filtering
* has been done.
*
* In our case, rBuffer is the buffer for the unpacked bytes and wBuffer is
* the final output bytes.
*/
/**
* Read in Huffman tables for RAR
* @param {bitjs.io.BitStream} bstream
*/
function rarReadTables(bstream) { function rarReadTables(bstream) {
var BitLength = new Array(rBC), var BitLength = new Array(rBC);
Table = new Array(rHuffTableSize); var Table = new Array(rHuffTableSize);
var i; var i;
// before we start anything we need to get byte-aligned // before we start anything we need to get byte-aligned
bstream.readBits( (8 - bstream.bitPtr) & 0x7 ); bstream.readBits((8 - bstream.bitPtr) & 0x7);
if (bstream.readBits(1)) { if (bstream.readBits(1)) {
info("Error! PPM not implemented yet"); info("Error! PPM not implemented yet");
@ -282,12 +325,13 @@ function rarReadTables(bstream) {
} }
if (!bstream.readBits(1)) { //discard old table if (!bstream.readBits(1)) { //discard old table
for (i = UnpOldTable.length; i--;) UnpOldTable[i] = 0; for (i = UnpOldTable.length; i--;) {
UnpOldTable[i] = 0;
}
} }
// read in bit lengths // read in bit lengths
for (var I = 0; I < rBC; ++I) { for (var I = 0; I < rBC; ++I) {
var Length = bstream.readBits(4); var Length = bstream.readBits(4);
if (Length === 15) { if (Length === 15) {
var ZeroCount = bstream.readBits(4); var ZeroCount = bstream.readBits(4);
@ -346,24 +390,26 @@ function rarReadTables(bstream) {
function rarDecodeNumber(bstream, dec) { function rarDecodeNumber(bstream, dec) {
var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum; var DecodeLen = dec.DecodeLen,
DecodePos = dec.DecodePos,
DecodeNum = dec.DecodeNum;
var bitField = bstream.getBits() & 0xfffe; var bitField = bstream.getBits() & 0xfffe;
//some sort of rolled out binary search //some sort of rolled out binary search
var bits = ((bitField < DecodeLen[8]) ? var bits = ((bitField < DecodeLen[8]) ?
((bitField < DecodeLen[4]) ? ((bitField < DecodeLen[4]) ?
((bitField < DecodeLen[2]) ? ((bitField < DecodeLen[2]) ?
((bitField < DecodeLen[1]) ? 1 : 2) ((bitField < DecodeLen[1]) ? 1 : 2) :
: ((bitField < DecodeLen[3]) ? 3 : 4)) ((bitField < DecodeLen[3]) ? 3 : 4)) :
: (bitField < DecodeLen[6]) ? (bitField < DecodeLen[6]) ?
((bitField < DecodeLen[5]) ? 5 : 6) ((bitField < DecodeLen[5]) ? 5 : 6) :
: ((bitField < DecodeLen[7]) ? 7 : 8)) ((bitField < DecodeLen[7]) ? 7 : 8)) :
: ((bitField < DecodeLen[12]) ? ((bitField < DecodeLen[12]) ?
((bitField < DecodeLen[10]) ? ((bitField < DecodeLen[10]) ?
((bitField < DecodeLen[9]) ? 9 : 10) ((bitField < DecodeLen[9]) ? 9 : 10) :
: ((bitField < DecodeLen[11]) ? 11 : 12)) ((bitField < DecodeLen[11]) ? 11 : 12)) :
: (bitField < DecodeLen[14]) ? (bitField < DecodeLen[14]) ?
((bitField < DecodeLen[13]) ? 13 : 14) ((bitField < DecodeLen[13]) ? 13 : 14) :
: 15)); 15));
bstream.readBits(bits); bstream.readBits(bits);
var N = DecodePos[bits] + ((bitField - DecodeLen[bits - 1]) >>> (16 - bits)); var N = DecodePos[bits] + ((bitField - DecodeLen[bits - 1]) >>> (16 - bits));
@ -372,12 +418,17 @@ function rarDecodeNumber(bstream, dec) {
function rarMakeDecodeTables(BitLength, offset, dec, size) { function rarMakeDecodeTables(BitLength, offset, dec, size) {
var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum; var DecodeLen = dec.DecodeLen;
var LenCount = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], var DecodePos = dec.DecodePos;
TmpPos = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], var DecodeNum = dec.DecodeNum;
N = 0, M = 0; var LenCount = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var TmpPos = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var N = 0;
var M = 0;
var i; var i;
for (i = DecodeNum.length; i--;) DecodeNum[i] = 0; for (i = DecodeNum.length; i--;) {
DecodeNum[i] = 0;
}
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
LenCount[BitLength[i + offset] & 0xF]++; LenCount[BitLength[i + offset] & 0xF]++;
} }
@ -398,24 +449,27 @@ function rarMakeDecodeTables(BitLength, offset, dec, size) {
TmpPos[I] = DecodePos[I]; TmpPos[I] = DecodePos[I];
} }
for (I = 0; I < size; ++I) { for (I = 0; I < size; ++I) {
if (BitLength[I + offset] !== 0) { if (BitLength[I + offset] != 0) {
DecodeNum[ TmpPos[ BitLength[offset + I] & 0xF ]++] = I; DecodeNum[TmpPos[BitLength[offset + I] & 0xF]++] = I;
} }
} }
} }
// TODO: implement // TODO: implement
/**
* @param {bitjs.io.BitStream} bstream
* @param {boolean} Solid
*/
function unpack15() { //bstream, Solid) { function unpack15() { //bstream, Solid) {
info("ERROR! RAR 1.5 compression not supported"); info("ERROR! RAR 1.5 compression not supported");
} }
var lowDistRepCount = 0, prevLowDist = 0; /**
* Unpacks the bit stream into rBuffer using the Unpack20 algorithm.
var rOldDist = [0, 0, 0, 0]; * @param {bitjs.io.BitStream} bstream
* @param {boolean} Solid
var lastDist = 0; */
var lastLength = 0;
function unpack20(bstream) { //, Solid) { function unpack20(bstream) { //, Solid) {
var destUnpSize = rBuffer.data.length; var destUnpSize = rBuffer.data.length;
var oldDistPtr = 0; var oldDistPtr = 0;
@ -441,7 +495,9 @@ function unpack20(bstream) { //, Solid) {
} }
if (Distance >= 0x2000) { if (Distance >= 0x2000) {
Length++; Length++;
if (Distance >= 0x40000) Length++; if (Distance >= 0x40000) {
Length++;
}
} }
lastLength = Length; lastLength = Length;
lastDist = rOldDist[oldDistPtr++ & 3] = Distance; lastDist = rOldDist[oldDistPtr++ & 3] = Distance;
@ -450,9 +506,7 @@ function unpack20(bstream) { //, Solid) {
} }
if (num === 269) { if (num === 269) {
rarReadTables20(bstream); rarReadTables20(bstream);
rarUpdateProgress(); rarUpdateProgress();
continue; continue;
} }
if (num === 256) { if (num === 256) {
@ -470,8 +524,10 @@ function unpack20(bstream) { //, Solid) {
if (Distance >= 0x101) { if (Distance >= 0x101) {
Length++; Length++;
if (Distance >= 0x2000) { if (Distance >= 0x2000) {
Length++; Length++
if (Distance >= 0x40000) Length++; if (Distance >= 0x40000) {
Length++;
}
} }
} }
lastLength = Length; lastLength = Length;
@ -500,7 +556,6 @@ function rarUpdateProgress() {
postProgress(); postProgress();
} }
var rNC20 = 298, var rNC20 = 298,
rDC20 = 48, rDC20 = 48,
rRC20 = 28, rRC20 = 28,
@ -516,7 +571,9 @@ function rarReadTables20(bstream) {
var i; var i;
bstream.readBits(1); bstream.readBits(1);
if (!bstream.readBits(1)) { if (!bstream.readBits(1)) {
for (i = UnpOldTable20.length; i--;) UnpOldTable20[i] = 0; for (i = UnpOldTable20.length; i--;) {
UnpOldTable20[i] = 0;
}
} }
TableSize = rNC20 + rDC20 + rRC20; TableSize = rNC20 + rDC20 + rRC20;
for (I = 0; I < rBC20; I++) { for (I = 0; I < rBC20; I++) {
@ -549,10 +606,253 @@ function rarReadTables20(bstream) {
rarMakeDecodeTables(Table, 0, LD, rNC20); rarMakeDecodeTables(Table, 0, LD, rNC20);
rarMakeDecodeTables(Table, rNC20, DD, rDC20); rarMakeDecodeTables(Table, rNC20, DD, rDC20);
rarMakeDecodeTables(Table, rNC20 + rDC20, RD, rRC20); rarMakeDecodeTables(Table, rNC20 + rDC20, RD, rRC20);
for (i = UnpOldTable20.length; i--;) UnpOldTable20[i] = Table[i]; for (i = UnpOldTable20.length; i--;) {
UnpOldTable20[i] = Table[i];
}
} }
var lowDistRepCount = 0;
var prevLowDist = 0;
var rOldDist = [0, 0, 0, 0];
var lastDist;
var lastLength;
// ============================================================================================== //
// Unpack code specific to RarVM
var VM = new RarVM();
/**
* Filters code, one entry per filter.
* @type {Array<UnpackFilter>}
*/
var Filters = [];
/**
* Filters stack, several entrances of same filter are possible.
* @type {Array<UnpackFilter>}
*/
var PrgStack = [];
/**
* Lengths of preceding blocks, one length per filter. Used to reduce
* size required to write block length if lengths are repeating.
* @type {Array<number>}
*/
var OldFilterLengths = [];
var LastFilter = 0;
function InitFilters() {
OldFilterLengths = [];
LastFilter = 0;
Filters = [];
PrgStack = [];
}
/**
* @param {number} firstByte The first byte (flags).
* @param {Uint8Array} vmCode An array of bytes.
*/
function rarAddVMCode(firstByte, vmCode) {
VM.init();
var bstream = new bitjs.io.BitStream(vmCode.buffer, true /* rtl */ );
var filtPos;
if (firstByte & 0x80) {
filtPos = RarVM.readData(bstream);
if (filtPos == 0) {
InitFilters();
} else {
filtPos--;
}
} else {
filtPos = LastFilter;
}
if (filtPos > Filters.length || filtPos > OldFilterLengths.length) {
return false;
}
LastFilter = filtPos;
var newFilter = (filtPos == Filters.length);
// new filter for PrgStack
var stackFilter = new UnpackFilter();
var filter = null;
// new filter code, never used before since VM reset
if (newFilter) {
// too many different filters, corrupt archive
if (filtPos > 1024) {
return false;
}
filter = new UnpackFilter();
Filters.push(filter);
stackFilter.ParentFilter = (Filters.length - 1);
OldFilterLengths.push(0); // OldFilterLengths.Add(1)
filter.ExecCount = 0;
} else { // filter was used in the past
filter = Filters[filtPos];
stackFilter.ParentFilter = filtPos;
filter.ExecCount++;
}
var emptyCount = 0;
for (var i = 0; i < PrgStack.length; ++i) {
PrgStack[i - emptyCount] = PrgStack[i];
if (PrgStack[i] == null) {
emptyCount++;
}
if (emptyCount > 0) {
PrgStack[i] = null;
}
}
if (emptyCount == 0) {
PrgStack.push(null); //PrgStack.Add(1);
emptyCount = 1;
}
var stackPos = PrgStack.length - emptyCount;
PrgStack[stackPos] = stackFilter;
stackFilter.ExecCount = filter.ExecCount;
var blockStart = RarVM.readData(bstream);
if (firstByte & 0x40) {
blockStart += 258;
}
stackFilter.BlockStart = (blockStart + rBuffer.ptr) & MAXWINMASK;
if (firstByte & 0x20) {
stackFilter.BlockLength = RarVM.readData(bstream);
} else {
stackFilter.BlockLength = filtPos < OldFilterLengths.length ?
OldFilterLengths[filtPos] :
0;
}
stackFilter.NextWindow = (wBuffer.ptr != rBuffer.ptr) &&
(((wBuffer.ptr - rBuffer.ptr) & MAXWINMASK) <= blockStart);
OldFilterLengths[filtPos] = stackFilter.BlockLength;
for (var i = 0; i < 7; ++i) {
stackFilter.Prg.InitR[i] = 0;
}
stackFilter.Prg.InitR[3] = VM_GLOBALMEMADDR;
stackFilter.Prg.InitR[4] = stackFilter.BlockLength;
stackFilter.Prg.InitR[5] = stackFilter.ExecCount;
// set registers to optional parameters if any
if (firstByte & 0x10) {
var initMask = bstream.readBits(7);
for (var i = 0; i < 7; ++i) {
if (initMask & (1 << i)) {
stackFilter.Prg.InitR[i] = RarVM.readData(bstream);
}
}
}
if (newFilter) {
var vmCodeSize = RarVM.readData(bstream);
if (vmCodeSize >= 0x10000 || vmCodeSize == 0) {
return false;
}
var vmCode = new Uint8Array(vmCodeSize);
for (var i = 0; i < vmCodeSize; ++i) {
//if (Inp.Overflow(3))
// return(false);
vmCode[i] = bstream.readBits(8);
}
VM.prepare(vmCode, filter.Prg);
}
stackFilter.Prg.Cmd = filter.Prg.Cmd;
stackFilter.Prg.AltCmd = filter.Prg.Cmd;
var staticDataSize = filter.Prg.StaticData.length;
if (staticDataSize > 0 && staticDataSize < VM_GLOBALMEMSIZE) {
// read statically defined data contained in DB commands
for (var i = 0; i < staticDataSize; ++i) {
stackFilter.Prg.StaticData[i] = filter.Prg.StaticData[i];
}
}
if (stackFilter.Prg.GlobalData.length < VM_FIXEDGLOBALSIZE) {
stackFilter.Prg.GlobalData = new Uint8Array(VM_FIXEDGLOBALSIZE);
}
var globalData = stackFilter.Prg.GlobalData;
for (var i = 0; i < 7; ++i) {
VM.setLowEndianValue(globalData, stackFilter.Prg.InitR[i], i * 4);
}
VM.setLowEndianValue(globalData, stackFilter.BlockLength, 0x1c);
VM.setLowEndianValue(globalData, 0, 0x20);
VM.setLowEndianValue(globalData, stackFilter.ExecCount, 0x2c);
for (var i = 0; i < 16; ++i) {
globalData[0x30 + i] = 0;
}
// put data block passed as parameter if any
if (firstByte & 8) {
//if (Inp.Overflow(3))
// return(false);
var dataSize = RarVM.readData(bstream);
if (dataSize > (VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE)) {
return (false);
}
var curSize = stackFilter.Prg.GlobalData.length;
if (curSize < dataSize + VM_FIXEDGLOBALSIZE) {
// Resize global data and update the stackFilter and local variable.
var numBytesToAdd = dataSize + VM_FIXEDGLOBALSIZE - curSize;
var newGlobalData = new Uint8Array(globalData.length + numBytesToAdd);
newGlobalData.set(globalData);
stackFilter.Prg.GlobalData = newGlobalData;
globalData = newGlobalData;
}
//byte *GlobalData=&StackFilter->Prg.GlobalData[VM_FIXEDGLOBALSIZE];
for (var i = 0; i < dataSize; ++i) {
//if (Inp.Overflow(3))
// return(false);
globalData[VM_FIXEDGLOBALSIZE + i] = bstream.readBits(8);
}
}
return true;
}
/**
* @param {!bitjs.io.BitStream} bstream
*/
function rarReadVMCode(bstream) {
var firstByte = bstream.readBits(8);
var length = (firstByte & 7) + 1;
if (length == 7) {
length = bstream.readBits(8) + 7;
} else if (length == 8) {
length = bstream.readBits(16);
}
// Read all bytes of VM code into an array.
var vmCode = new Uint8Array(length);
for (var i = 0; i < length; i++) {
// Do something here with checking readbuf.
vmCode[i] = bstream.readBits(8);
}
return RarAddVMCode(firstByte, vmCode);
}
/**
* Unpacks the bit stream into rBuffer using the Unpack29 algorithm.
* @param {bitjs.io.BitStream} bstream
* @param {boolean} Solid
*/
function unpack29(bstream) { function unpack29(bstream) {
// lazy initialize rDDecode and rDBits // lazy initialize rDDecode and rDBits
@ -577,7 +877,9 @@ function unpack29(bstream) {
lastDist = 0; lastDist = 0;
lastLength = 0; lastLength = 0;
var i; var i;
for (i = UnpOldTable.length; i--;) UnpOldTable[i] = 0; for (i = UnpOldTable.length; i--;) {
UnpOldTable[i] = 0;
}
// read in Huffman tables // read in Huffman tables
rarReadTables(bstream); rarReadTables(bstream);
@ -632,12 +934,15 @@ function unpack29(bstream) {
continue; continue;
} }
if (num === 256) { if (num === 256) {
if (!rarReadEndOfBlock(bstream)) break; if (!rarReadEndOfBlock(bstream)) {
break;
}
continue; continue;
} }
if (num === 257) { if (num === 257) {
//console.log("READVMCODE"); if (!rarReadVMCode(bstream)) {
if (!rarReadVMCode(bstream)) break; break;
}
continue; continue;
} }
if (num === 258) { if (num === 258) {
@ -674,15 +979,193 @@ function unpack29(bstream) {
rarCopyString(2, Distance); rarCopyString(2, Distance);
continue; continue;
} }
} } // while (true)
rarUpdateProgress(); rarUpdateProgress();
rarWriteBuf();
} }
function rarReadEndOfBlock(bstream) { /**
* Does stuff to the current byte buffer (rBuffer) based on
* the filters loaded into the RarVM and writes out to wBuffer.
*/
function rarWriteBuf() {
var writeSize = (rBuffer.ptr & MAXWINMASK);
for (var i = 0; i < PrgStack.length; ++i) {
var flt = PrgStack[i];
if (flt == null) {
continue;
}
if (flt.NextWindow) {
flt.NextWindow = false;
continue;
}
var blockStart = flt.BlockStart;
var blockLength = flt.BlockLength;
// WrittenBorder = wBuffer.ptr
if (((blockStart - wBuffer.ptr) & MAXWINMASK) < writeSize) {
if (wBuffer.ptr != blockStart) {
// Copy blockStart bytes from rBuffer into wBuffer.
rarWriteArea(wBuffer.ptr, blockStart);
writeSize = (rBuffer.ptr - wBuffer.ptr) & MAXWINMASK;
}
if (blockLength <= writeSize) {
var blockEnd = (blockStart + blockLength) & MAXWINMASK;
if (blockStart < blockEnd || blockEnd == 0) {
VM.setMemory(0, rBuffer.data.subarray(blockStart, blockStart + blockLength), blockLength);
} else {
var firstPartLength = MAXWINSIZE - blockStart;
VM.setMemory(0, rBuffer.data.subarray(blockStart, blockStart + firstPartLength), firstPartLength);
VM.setMemory(firstPartLength, rBuffer.data, blockEnd);
}
var parentPrg = Filters[flt.ParentFilter].Prg;
var prg = flt.Prg;
if (parentPrg.GlobalData.length > VM_FIXEDGLOBALSIZE) {
// Copy global data from previous script execution if any.
prg.GlobalData = new Uint8Array(parentPrg.GlobalData);
}
rarExecuteCode(prg);
if (prg.GlobalData.length > VM_FIXEDGLOBALSIZE) {
// Save global data for next script execution.
var globalDataLen = prg.GlobalData.length;
if (parentPrg.GlobalData.length < globalDataLen) {
parentPrg.GlobalData = new Uint8Array(globalDataLen);
}
parentPrg.GlobalData.set(
this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen),
VM_FIXEDGLOBALSIZE);
} else {
parentPrg.GlobalData = new Uint8Array(0);
}
var filteredData = prg.FilteredData;
PrgStack[i] = null;
while (i + 1 < PrgStack.length) {
var nextFilter = PrgStack[i + 1];
if (nextFilter == null || nextFilter.BlockStart != blockStart ||
nextFilter.BlockLength != filteredData.length || nextFilter.NextWindow) {
break;
}
// Apply several filters to same data block.
VM.setMemory(0, filteredData, filteredData.length);
var parentPrg = Filters[nextFilter.ParentFilter].Prg;
var nextPrg = nextFilter.Prg;
var globalDataLen = parentPrg.GlobalData.length;
if (globalDataLen > VM_FIXEDGLOBALSIZE) {
// Copy global data from previous script execution if any.
nextPrg.GlobalData = new Uint8Array(globalDataLen);
nextPrg.GlobalData.set(parentPrg.GlobalData.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen), VM_FIXEDGLOBALSIZE);
}
rarExecuteCode(nextPrg);
if (nextPrg.GlobalData.length > VM_GLOBALMEMSIZE) {
// Save global data for next script execution.
var globalDataLen = nextPrg.GlobalData.length;
if (parentPrg.GlobalData.length < globalDataLen) {
parentPrg.GlobalData = new Uint8Array(globalDataLen);
}
parentPrg.GlobalData.set(
this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen),
VM_FIXEDGLOBALSIZE);
} else {
parentPrg.GlobalData = new Uint8Array(0);
}
filteredData = nextPrg.FilteredData;
i++;
PrgStack[i] = null;
} // while (i + 1 < PrgStack.length)
for (var j = 0; j < filteredData.length; ++j) {
wBuffer.insertByte(filteredData[j]);
}
writeSize = (rBuffer.ptr - wBuffer.ptr) & MAXWINMASK;
} // if (blockLength <= writeSize)
else {
for (var j = i; j < PrgStack.length; ++j) {
var flt = PrgStack[j];
if (flt != null && flt.NextWindow) {
flt.NextWindow = false;
}
}
//WrPtr=WrittenBorder;
return;
}
} // if (((blockStart - wBuffer.ptr) & MAXWINMASK) < writeSize)
} // for (var i = 0; i < PrgStack.length; ++i)
// Write any remaining bytes from rBuffer to wBuffer;
rarWriteArea(wBuffer.ptr, rBuffer.ptr);
// Now that the filtered buffer has been written, swap it back to rBuffer.
rBuffer = wBuffer;
}
/**
* Copy bytes from rBuffer to wBuffer.
* @param {number} startPtr The starting point to copy from rBuffer.
* @param {number} endPtr The ending point to copy from rBuffer.
*/
function rarWriteArea(startPtr, endPtr) {
if (endPtr < startPtr) {
console.error('endPtr < startPtr, endPtr=' + endPtr + ', startPtr=' + startPtr);
// rarWriteData(startPtr, -(int)StartPtr & MAXWINMASK);
// RarWriteData(0, endPtr);
return;
} else if (startPtr < endPtr) {
rarWriteData(startPtr, endPtr - startPtr);
}
}
/**
* Writes bytes into wBuffer from rBuffer.
* @param {number} offset The starting point to copy bytes from rBuffer.
* @param {number} numBytes The number of bytes to copy.
*/
function rarWriteData(offset, numBytes) {
if (wBuffer.ptr >= rBuffer.data.length) {
return;
}
var leftToWrite = rBuffer.data.length - wBuffer.ptr;
if (numBytes > leftToWrite) {
numBytes = leftToWrite;
}
for (var i = 0; i < numBytes; ++i) {
wBuffer.insertByte(rBuffer.data[offset + i]);
}
}
/**
* @param {VM_PreparedProgram} prg
*/
function rarExecuteCode(prg) {
if (prg.GlobalData.length > 0) {
var writtenFileSize = wBuffer.ptr;
prg.InitR[6] = writtenFileSize;
VM.setLowEndianValue(prg.GlobalData, writtenFileSize, 0x24);
VM.setLowEndianValue(prg.GlobalData, (writtenFileSize >>> 32) >> 0, 0x28);
VM.execute(prg);
}
}
function rarReadEndOfBlock(bstream) {
rarUpdateProgress(); rarUpdateProgress();
var NewTable = false, NewFile = false; var NewTable = false,
NewFile = false;
if (bstream.readBits(1)) { if (bstream.readBits(1)) {
NewTable = true; NewTable = true;
} else { } else {
@ -693,31 +1176,6 @@ function rarReadEndOfBlock(bstream) {
return !(NewFile || (NewTable && !rarReadTables(bstream))); return !(NewFile || (NewTable && !rarReadTables(bstream)));
} }
function rarReadVMCode(bstream) {
var FirstByte = bstream.readBits(8);
var Length = (FirstByte & 7) + 1;
if (Length === 7) {
Length = bstream.readBits(8) + 7;
} else if (Length === 8) {
Length = bstream.readBits(16);
}
var vmCode = [];
for (var I = 0; I < Length; I++) {
//do something here with cheking readbuf
vmCode.push(bstream.readBits(8));
}
return rarAddVMCode(vmCode);
}
function rarAddVMCode(vmCode) {
//console.log(vmCode);
if (vmCode.length > 0) {
info("Error! RarVM not supported yet!");
}
return true;
}
function rarInsertLastMatch(length, distance) { function rarInsertLastMatch(length, distance) {
lastDist = distance; lastDist = distance;
lastLength = length; lastLength = length;
@ -728,32 +1186,42 @@ function rarInsertOldDist(distance) {
rOldDist.splice(0, 0, distance); rOldDist.splice(0, 0, distance);
} }
var rOldBuffers = []; /**
* Copies len bytes from distance bytes ago in the buffer to the end of the
//this is the real function, the other one is for debugging * current byte buffer.
* @param {number} length How many bytes to copy.
* @param {number} distance How far back in the buffer from the current write
* pointer to start copying from.
*/
function rarCopyString(length, distance) { function rarCopyString(length, distance) {
var destPtr = rBuffer.ptr - distance; var srcPtr = rBuffer.ptr - distance;
if (destPtr < 0) { if (srcPtr < 0) {
var l = rOldBuffers.length; var l = rOldBuffers.length;
while (destPtr < 0) { while (srcPtr < 0) {
destPtr = rOldBuffers[--l].data.length + destPtr; srcPtr = rOldBuffers[--l].data.length + srcPtr;
}
// TODO: lets hope that it never needs to read beyond file boundaries
while (length--) {
rBuffer.insertByte(rOldBuffers[l].data[srcPtr++]);
} }
//TODO: lets hope that it never needs to read beyond file boundaries
while (length--) rBuffer.insertByte(rOldBuffers[l].data[destPtr++]);
} }
if (length > distance) { if (length > distance) {
while (length--) rBuffer.insertByte(rBuffer.data[destPtr++]); while (length--) {
rBuffer.insertByte(rBuffer.data[srcPtr++]);
}
} else { } else {
rBuffer.insertBytes(rBuffer.data.subarray(destPtr, destPtr + length)); rBuffer.insertBytes(rBuffer.data.subarray(srcPtr, srcPtr + length));
} }
} }
// v must be a valid RarVolume /**
* @param {RarLocalFile} v
*/
function unpack(v) { function unpack(v) {
// TODO: implement what happens when unpVer is < 15 // TODO: implement what happens when unpVer is < 15
var Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer, var Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer;
bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength ); var Solid = v.header.LHD_SOLID;
var bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */ , v.fileData.byteOffset, v.fileData.byteLength);
rBuffer = new bitjs.io.ByteBuffer(v.header.unpackedSize); rBuffer = new bitjs.io.ByteBuffer(v.header.unpackedSize);
@ -769,18 +1237,18 @@ function unpack(v) {
break; break;
case 29: // rar 3.x compression case 29: // rar 3.x compression
case 36: // alternative hash case 36: // alternative hash
wBuffer = new bitjs.io.ByteBuffer(rBuffer.data.length);
unpack29(bstream); unpack29(bstream);
break; break;
} // switch(method) } // switch(method)
rOldBuffers.push(rBuffer); rOldBuffers.push(rBuffer);
//TODO: clear these old buffers when there's over 4MB of history // TODO: clear these old buffers when there's over 4MB of history
return rBuffer.data; return rBuffer.data;
} }
// bstream is a bit stream // bstream is a bit stream
var RarLocalFile = function(bstream) { var RarLocalFile = function(bstream) {
this.header = new RarVolumeHeader(bstream); this.header = new RarVolumeHeader(bstream);
this.filename = this.header.filename; this.filename = this.header.filename;
@ -798,7 +1266,6 @@ var RarLocalFile = function(bstream) {
}; };
RarLocalFile.prototype.unrar = function() { RarLocalFile.prototype.unrar = function() {
if (!this.header.flags.LHD_SPLIT_BEFORE) { if (!this.header.flags.LHD_SPLIT_BEFORE) {
// unstore file // unstore file
if (this.header.method === 0x30) { if (this.header.method === 0x30) {
@ -836,7 +1303,6 @@ var unrar = function(arrayBuffer) {
header.headType === 0x72 && header.headType === 0x72 &&
header.flags.value === 0x1A21 && header.flags.value === 0x1A21 &&
header.headSize === 7) { header.headSize === 7) {
info("Found RAR signature"); info("Found RAR signature");
var mhead = new RarVolumeHeader(bstream); var mhead = new RarVolumeHeader(bstream);
@ -859,7 +1325,7 @@ var unrar = function(arrayBuffer) {
break; break;
} }
//info("bstream" + bstream.bytePtr+"/"+bstream.bytes.length); //info("bstream" + bstream.bytePtr+"/"+bstream.bytes.length);
} while ( localFile.isValid ); } while (localFile.isValid);
totalFilesInArchive = localFiles.length; totalFilesInArchive = localFiles.length;
// now we have all information but things are unpacked // now we have all information but things are unpacked

@ -0,0 +1,167 @@
/**
* untar.js
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
*
* Reference Documentation:
*
* TAR format: http://www.gnu.org/software/automake/manual/tar/Standard.html
*/
// This file expects to be invoked as a Worker (see onmessage below).
importScripts('../io/bytestream.js');
importScripts('archive.js');
// Progress variables.
var currentFilename = "";
var currentFileNumber = 0;
var currentBytesUnarchivedInFile = 0;
var currentBytesUnarchived = 0;
var totalUncompressedBytesInArchive = 0;
var totalFilesInArchive = 0;
// Helper functions.
var info = function(str) {
postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
};
var err = function(str) {
postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
};
// Removes all characters from the first zero-byte in the string onwards.
var readCleanString = function(bstr, numBytes) {
var str = bstr.readString(numBytes);
var zIndex = str.indexOf(String.fromCharCode(0));
return zIndex != -1 ? str.substr(0, zIndex) : str;
};
var postProgress = function() {
postMessage(new bitjs.archive.UnarchiveProgressEvent(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive,
));
};
// takes a ByteStream and parses out the local file information
var TarLocalFile = function(bstream) {
this.isValid = false;
var bytesRead = 0;
// Read in the header block
this.name = readCleanString(bstream, 100);
this.mode = readCleanString(bstream, 8);
this.uid = readCleanString(bstream, 8);
this.gid = readCleanString(bstream, 8);
this.size = parseInt(readCleanString(bstream, 12), 8);
this.mtime = readCleanString(bstream, 12);
this.chksum = readCleanString(bstream, 8);
this.typeflag = readCleanString(bstream, 1);
this.linkname = readCleanString(bstream, 100);
this.maybeMagic = readCleanString(bstream, 6);
if (this.maybeMagic == "ustar") {
this.version = readCleanString(bstream, 2);
this.uname = readCleanString(bstream, 32);
this.gname = readCleanString(bstream, 32);
this.devmajor = readCleanString(bstream, 8);
this.devminor = readCleanString(bstream, 8);
this.prefix = readCleanString(bstream, 155);
if (this.prefix.length) {
this.name = this.prefix + this.name;
}
bstream.readBytes(12); // 512 - 500
} else {
bstream.readBytes(255); // 512 - 257
}
bytesRead += 512;
// Done header, now rest of blocks are the file contents.
this.filename = this.name;
this.fileData = null;
info("Untarring file '" + this.filename + "'");
info(" size = " + this.size);
info(" typeflag = " + this.typeflag);
// A regular file.
if (this.typeflag == 0) {
info(" This is a regular file.");
var sizeInBytes = parseInt(this.size);
this.fileData = new Uint8Array(bstream.readBytes(sizeInBytes));
bytesRead += sizeInBytes;
if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) {
this.isValid = true;
}
// Round up to 512-byte blocks.
var remaining = 512 - bytesRead % 512;
if (remaining > 0 && remaining < 512) {
bstream.readBytes(remaining);
}
} else if (this.typeflag == 5) {
info(" This is a directory.");
}
}
var untar = function(arrayBuffer) {
postMessage(new bitjs.archive.UnarchiveStartEvent());
currentFilename = "";
currentFileNumber = 0;
currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0;
allLocalFiles = [];
var bstream = new bitjs.io.ByteStream(arrayBuffer);
postProgress();
// While we don't encounter an empty block, keep making TarLocalFiles.
while (bstream.peekNumber(4) != 0) {
var oneLocalFile = new TarLocalFile(bstream);
if (oneLocalFile && oneLocalFile.isValid) {
// If we make it to this point and haven't thrown an error, we have successfully
// read in the data for a local file, so we can update the actual bytestream.
allLocalFiles.push(oneLocalFile);
totalUncompressedBytesInArchive += oneLocalFile.size;
// update progress
currentFilename = oneLocalFile.filename;
currentFileNumber = totalFilesInArchive++;
currentBytesUnarchivedInFile = oneLocalFile.size;
currentBytesUnarchived += oneLocalFile.size;
postMessage(new bitjs.archive.UnarchiveExtractEvent(oneLocalFile));
postProgress();
}
}
totalFilesInArchive = allLocalFiles.length;
postProgress();
postMessage(new bitjs.archive.UnarchiveFinishEvent());
};
// event.data.file has the first ArrayBuffer.
// event.data.bytes has all subsequent ArrayBuffers.
onmessage = function(event) {
try {
untar(event.data.file, true);
} catch (e) {
if (typeof e === "string" && e.startsWith("Error! Overflowed")) {
// Overrun the buffer.
// unarchiveState = UnarchiveState.WAITING;
} else {
console.error("Found an error while untarring");
console.log(e);
throw e;
}
}
};

@ -1,6 +1,8 @@
/** /**
* unzip.js * unzip.js
* *
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc. * Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15 * Copyright(c) 2011 antimatter15
* *
@ -12,8 +14,10 @@
/* global bitjs, importScripts, Uint8Array*/ /* global bitjs, importScripts, Uint8Array*/
// This file expects to be invoked as a Worker (see onmessage below). // This file expects to be invoked as a Worker (see onmessage below).
importScripts("io.js"); importScripts('../io/bitstream.js');
importScripts("archive.js"); importScripts('../io/bytebuffer.js');
importScripts('../io/bytestream.js');
importScripts('archive.js');
// Progress variables. // Progress variables.
var currentFilename = ""; var currentFilename = "";
@ -283,7 +287,8 @@ function getHuffmanCodes(bitLengths) {
} }
// Step 3: Assign numerical values to all codes // Step 3: Assign numerical values to all codes
var table = {}, tableLength = 0; var table = {},
tableLength = 0;
for (var n = 0; n < numLengths; ++n) { for (var n = 0; n < numLengths; ++n) {
var len = bitLengths[n]; var len = bitLengths[n];
if (len !== 0) { if (len !== 0) {
@ -316,6 +321,7 @@ function getHuffmanCodes(bitLengths) {
// fixed Huffman codes go from 7-9 bits, so we need an array whose index can hold up to 9 bits // fixed Huffman codes go from 7-9 bits, so we need an array whose index can hold up to 9 bits
var fixedHCtoLiteral = null; var fixedHCtoLiteral = null;
var fixedHCtoDistance = null; var fixedHCtoDistance = null;
function getFixedLiteralTable() { function getFixedLiteralTable() {
// create once // create once
if (!fixedHCtoLiteral) { if (!fixedHCtoLiteral) {
@ -331,6 +337,7 @@ function getFixedLiteralTable() {
} }
return fixedHCtoLiteral; return fixedHCtoLiteral;
} }
function getFixedDistanceTable() { function getFixedDistanceTable() {
// create once // create once
if (!fixedHCtoDistance) { if (!fixedHCtoDistance) {
@ -387,16 +394,36 @@ Code Bits Length(s) Code Bits Lengths Code Bits Length(s)
264 0 10 274 3 43-50 284 5 227-257 264 0 10 274 3 43-50 284 5 227-257
265 1 11,12 275 3 51-58 285 0 258 265 1 11,12 275 3 51-58 285 0 258
266 1 13,14 276 3 59-66 266 1 13,14 276 3 59-66
*/ */
var LengthLookupTable = [ var LengthLookupTable = [
[0, 3], [0, 4], [0, 5], [0, 6], [0, 3],
[0, 7], [0, 8], [0, 9], [0, 10], [0, 4],
[1, 11], [1, 13], [1, 15], [1, 17], [0, 5],
[2, 19], [2, 23], [2, 27], [2, 31], [0, 6],
[3, 35], [3, 43], [3, 51], [3, 59], [0, 7],
[4, 67], [4, 83], [4, 99], [4, 115], [0, 8],
[5, 131], [5, 163], [5, 195], [5, 227], [0, 9],
[0, 10],
[1, 11],
[1, 13],
[1, 15],
[1, 17],
[2, 19],
[2, 23],
[2, 27],
[2, 31],
[3, 35],
[3, 43],
[3, 51],
[3, 59],
[4, 67],
[4, 83],
[4, 99],
[4, 115],
[5, 131],
[5, 163],
[5, 195],
[5, 227],
[0, 258] [0, 258]
]; ];
/* /*
@ -415,20 +442,36 @@ var LengthLookupTable = [
9 3 25-32 19 8 769-1024 29 13 24577-32768 9 3 25-32 19 8 769-1024 29 13 24577-32768
*/ */
var DistLookupTable = [ var DistLookupTable = [
[0, 1], [0, 2], [0, 3], [0, 4], [0, 1],
[1, 5], [1, 7], [0, 2],
[2, 9], [2, 13], [0, 3],
[3, 17], [3, 25], [0, 4],
[4, 33], [4, 49], [1, 5],
[5, 65], [5, 97], [1, 7],
[6, 129], [6, 193], [2, 9],
[7, 257], [7, 385], [2, 13],
[8, 513], [8, 769], [3, 17],
[9, 1025], [9, 1537], [3, 25],
[10, 2049], [10, 3073], [4, 33],
[11, 4097], [11, 6145], [4, 49],
[12, 8193], [12, 12289], [5, 65],
[13, 16385], [13, 24577] [5, 97],
[6, 129],
[6, 193],
[7, 257],
[7, 385],
[8, 513],
[8, 769],
[9, 1025],
[9, 1537],
[10, 2049],
[10, 3073],
[11, 4097],
[11, 6145],
[12, 8193],
[12, 12289],
[13, 16385],
[13, 24577]
]; ];
function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) { function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
@ -487,7 +530,6 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
} else { } else {
buffer.insertBytes(buffer.data.subarray(ch, ch + length)); buffer.insertBytes(buffer.data.subarray(ch, ch + length));
} }
} // length-distance pair } // length-distance pair
} // length-distance pair or end-of-block } // length-distance pair or end-of-block
} // loop until we reach end of block } // loop until we reach end of block

@ -1,308 +0,0 @@
/*
* bytestream.js
*
* Provides readers for byte streams.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
/**
* This object allows you to peek and consume bytes as numbers and strings out
* of a stream. More bytes can be pushed into the back of the stream via the
* push() method.
*/
bitjs.io.ByteStream = class {
/**
* @param {ArrayBuffer} ab The ArrayBuffer object.
* @param {number=} opt_offset The offset into the ArrayBuffer
* @param {number=} opt_length The length of this BitStream
*/
constructor(ab, opt_offset, opt_length) {
if (!(ab instanceof ArrayBuffer)) {
throw 'Error! BitArray constructed with an invalid ArrayBuffer object';
}
const offset = opt_offset || 0;
const length = opt_length || ab.byteLength;
/**
* The current page of bytes in the stream.
* @type {Uint8Array}
* @private
*/
this.bytes = new Uint8Array(ab, offset, length);
/**
* The next pages of bytes in the stream.
* @type {Array<Uint8Array>}
* @private
*/
this.pages_ = [];
/**
* The byte in the current page that we will read next.
* @type {Number}
* @private
*/
this.ptr = 0;
/**
* An ever-increasing number.
* @type {Number}
* @private
*/
this.bytesRead_ = 0;
}
/**
* Returns how many bytes have been read in the stream since the beginning of time.
*/
getNumBytesRead() {
return this.bytesRead_;
}
/**
* Returns how many bytes are currently in the stream left to be read.
*/
getNumBytesLeft() {
const bytesInCurrentPage = (this.bytes.byteLength - this.ptr);
return this.pages_.reduce((acc, arr) => acc + arr.length, bytesInCurrentPage);
}
/**
* Move the pointer ahead n bytes. If the pointer is at the end of the current array
* of bytes and we have another page of bytes, point at the new page. This is a private
* method, no validation is done.
* @param {number} n Number of bytes to increment.
* @private
*/
movePointer_(n) {
this.ptr += n;
this.bytesRead_ += n;
while (this.ptr >= this.bytes.length && this.pages_.length > 0) {
this.ptr -= this.bytes.length;
this.bytes = this.pages_.shift();
}
}
/**
* Peeks at the next n bytes as an unsigned number but does not advance the
* pointer.
* @param {number} n The number of bytes to peek at. Must be a positive integer.
* @return {number} The n bytes interpreted as an unsigned number.
*/
peekNumber(n) {
const num = parseInt(n, 10);
if (n !== num || num < 0) {
throw 'Error! Called peekNumber() with a non-positive integer';
} else if (num === 0) {
return 0;
}
if (n > 4) {
throw 'Error! Called peekNumber(' + n +
') but this method can only reliably read numbers up to 4 bytes long';
}
if (this.getNumBytesLeft() < num) {
throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
}
let result = 0;
let curPage = this.bytes;
let pageIndex = 0;
let ptr = this.ptr;
for (let i = 0; i < num; ++i) {
result |= (curPage[ptr++] << (i * 8));
if (ptr >= curPage.length) {
curPage = this.pages_[pageIndex++];
ptr = 0;
}
}
return result;
}
/**
* Returns the next n bytes as an unsigned number (or -1 on error)
* and advances the stream pointer n bytes.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {number} The n bytes interpreted as an unsigned number.
*/
readNumber(n) {
const num = this.peekNumber(n);
this.movePointer_(n);
return num;
}
/**
* Returns the next n bytes as a signed number but does not advance the
* pointer.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {number} The bytes interpreted as a signed number.
*/
peekSignedNumber(n) {
let num = this.peekNumber(n);
const HALF = Math.pow(2, (n * 8) - 1);
const FULL = HALF * 2;
if (num >= HALF) num -= FULL;
return num;
}
/**
* Returns the next n bytes as a signed number and advances the stream pointer.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {number} The bytes interpreted as a signed number.
*/
readSignedNumber(n) {
const num = this.peekSignedNumber(n);
this.movePointer_(n);
return num;
}
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @param {boolean} movePointers Whether to move the pointers.
* @return {Uint8Array} The subarray.
*/
peekBytes(n, movePointers) {
const num = parseInt(n, 10);
if (n !== num || num < 0) {
throw 'Error! Called peekBytes() with a non-positive integer';
} else if (num === 0) {
return new Uint8Array();
}
const totalBytesLeft = this.getNumBytesLeft();
if (num > totalBytesLeft) {
throw 'Error! Overflowed the byte stream during peekBytes! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
}
const result = new Uint8Array(num);
let curPage = this.bytes;
let ptr = this.ptr;
let bytesLeftToCopy = num;
let pageIndex = 0;
while (bytesLeftToCopy > 0) {
const bytesLeftInPage = curPage.length - ptr;
const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInPage);
result.set(curPage.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy);
ptr += sourceLength;
if (ptr >= curPage.length) {
curPage = this.pages_[pageIndex++];
ptr = 0;
}
bytesLeftToCopy -= sourceLength;
}
if (movePointers) {
this.movePointer_(num);
}
return result;
}
/**
* Reads the next n bytes as a sub-array.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {Uint8Array} The subarray.
*/
readBytes(n) {
return this.peekBytes(n, true);
}
/**
* Peeks at the next n bytes as an ASCII string but does not advance the pointer.
* @param {number} n The number of bytes to peek at. Must be a positive integer.
* @return {string} The next n bytes as a string.
*/
peekString(n) {
const num = parseInt(n, 10);
if (n !== num || num < 0) {
throw 'Error! Called peekString() with a non-positive integer';
} else if (num === 0) {
return '';
}
const totalBytesLeft = this.getNumBytesLeft();
if (num > totalBytesLeft) {
throw 'Error! Overflowed the byte stream while peekString()! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
}
let result = new Array(num);
let curPage = this.bytes;
let pageIndex = 0;
let ptr = this.ptr;
for (let i = 0; i < num; ++i) {
result[i] = String.fromCharCode(curPage[ptr++]);
if (ptr >= curPage.length) {
curPage = this.pages_[pageIndex++];
ptr = 0;
}
}
return result.join('');
}
/**
* Returns the next n bytes as an ASCII string and advances the stream pointer
* n bytes.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {string} The next n bytes as a string.
*/
readString(n) {
const strToReturn = this.peekString(n);
this.movePointer_(n);
return strToReturn;
}
/**
* Feeds more bytes into the back of the stream.
* @param {ArrayBuffer} ab
*/
push(ab) {
if (!(ab instanceof ArrayBuffer)) {
throw 'Error! ByteStream.push() called with an invalid ArrayBuffer object';
}
this.pages_.push(new Uint8Array(ab));
// If the pointer is at the end of the current page of bytes, this will advance
// to the next page.
this.movePointer_(0);
}
/**
* Creates a new ByteStream from this ByteStream that can be read / peeked.
* @return {bitjs.io.ByteStream} A clone of this ByteStream.
*/
tee() {
const clone = new bitjs.io.ByteStream(this.bytes.buffer);
clone.bytes = this.bytes;
clone.ptr = this.ptr;
clone.pages_ = this.pages_.slice();
clone.bytesRead_ = this.bytesRead_;
return clone;
}
}

@ -58,8 +58,8 @@ $( 'a.navbar-brand' ).clone().appendTo( '.home-btn' ).empty().removeClass('navba
if ( $( 'body.book' ).length > 0 ) { if ( $( 'body.book' ).length > 0 ) {
description = $( '.comments' ); description = $( '.comments' );
bookInfo = $( '.author' ).nextUntil( 'h3:contains("Description")'); bookInfo = $( ".author" ).nextUntil("#decription");
$( 'h3:contains("Description")' ).detach(); $("#decription").detach();
$( '.comments' ).detach(); $( '.comments' ).detach();
$( bookInfo ).wrapAll( '<div class="bookinfo"></div>' ); $( bookInfo ).wrapAll( '<div class="bookinfo"></div>' );
// $( 'h3:contains("Description:")' ).after( '<div class="description"></div>' ); // $( 'h3:contains("Description:")' ).after( '<div class="description"></div>' );
@ -145,14 +145,29 @@ if ( $( 'body.book' ).length > 0 ) {
.prepend( '<div><img class="bg-blur" src="' + cover + '"></div>' ); .prepend( '<div><img class="bg-blur" src="' + cover + '"></div>' );
// Fix-up book detail headings // Fix-up book detail headings
publisher = $( '.publishers p span' ).text().split( ':' ); publisher = $( '.publishers p span' ).text().split( ':' );
$( '.publishers p span' ).remove(); $( '.publishers p span' ).remove();
$.each(publisher, function(i, val) { $.each(publisher, function(i, val) {
$( '.publishers' ).append( '<span>' + publisher[i] + '</span>' ); $( '.publishers' ).append( '<span>' + publisher[i] + '</span>' );
});
$( '.publishers span:nth-child(3)' ).text(function() {
return $(this).text().replace(/^\s+|^\t+|\t+|\s+$/g, "");
}); });
$( '.publishers span:nth-child(3)' ).text(function() {
return $(this).text().replace(/^\s+|^\t+|\t+|\s+$/g, ""); // Fix-up book custom colums headings
}); // real_custom_column = $( '.real_custom_columns' ).text().split( ':' );
real_custom_column = $( '.real_custom_columns' );
// $( '.real_custom_columns' ).remove();
$.each(real_custom_column, function(i, val) {
real_cc = $(this).text().split( ':' );
$( this ).text("");
if (real_cc.length > 1) {
$( this ).append( '<span>' + real_cc[0] + '</span><span>' + real_cc[1] + '</span>' );
}
});
//$( '.real_custom_columns:nth-child(3)' ).text(function() {
//return $(this).text().replace(/^\s+|^\t+|\t+|\s+$/g, "");
//});
published = $( '.publishing-date p' ) published = $( '.publishing-date p' )
.text().split(': '); .text().split(': ');

@ -0,0 +1,233 @@
/*
* bitstream.js
*
* Provides readers for bitstreams.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
(function() {
// mask for getting the Nth bit (zero-based)
bitjs.BIT = [0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80,
0x100, 0x200, 0x400, 0x800,
0x1000, 0x2000, 0x4000, 0x8000
];
// mask for getting N number of bits (0-8)
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF];
/**
* This bit stream peeks and consumes bits out of a binary stream.
*
* @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array.
* @param {boolean} rtl Whether the stream reads bits from the byte starting
* from bit 7 to 0 (true) or bit 0 to 7 (false).
* @param {Number} opt_offset The offset into the ArrayBuffer
* @param {Number} opt_length The length of this BitStream
*/
bitjs.io.BitStream = function(ab, rtl, opt_offset, opt_length) {
if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") {
throw "Error! BitArray constructed with an invalid ArrayBuffer object";
}
var offset = opt_offset || 0;
var length = opt_length || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length);
this.bytePtr = 0; // tracks which byte we are on
this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7)
this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr;
};
/**
* byte0 byte1 byte2 byte3
* 7......0 | 7......0 | 7......0 | 7......0
*
* The bit pointer starts at bit0 of byte0 and moves left until it reaches
* bit7 of byte0, then jumps to bit0 of byte1, etc.
* @param {number} n The number of bits to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {number} The peeked bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.peekBits_ltr = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return 0;
}
var movePointers = movePointers || false,
bytePtr = this.bytePtr,
bitPtr = this.bitPtr,
result = 0,
bitsIn = 0,
bytes = this.bytes;
// keep going until we have no more bits left to peek at
// TODO: Consider putting all bits from bytes we will need into a variable and then
// shifting/masking it to just extract the bits we want.
// This could be considerably faster when reading more than 3 or 4 bits at a time.
while (n > 0) {
if (bytePtr >= bytes.length) {
throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
bytes.length + ", bitPtr=" + bitPtr;
return -1;
}
var numBitsLeftInThisByte = (8 - bitPtr);
if (n >= numBitsLeftInThisByte) {
var mask = (BITMASK[numBitsLeftInThisByte] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bytePtr++;
bitPtr = 0;
bitsIn += numBitsLeftInThisByte;
n -= numBitsLeftInThisByte;
} else {
var mask = (BITMASK[n] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bitPtr += n;
bitsIn += n;
n = 0;
}
}
if (movePointers) {
this.bitPtr = bitPtr;
this.bytePtr = bytePtr;
}
return result;
};
/**
* byte0 byte1 byte2 byte3
* 7......0 | 7......0 | 7......0 | 7......0
*
* The bit pointer starts at bit7 of byte0 and moves right until it reaches
* bit0 of byte0, then goes to bit7 of byte1, etc.
* @param {number} n The number of bits to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {number} The peeked bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.peekBits_rtl = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return 0;
}
var movePointers = movePointers || false,
bytePtr = this.bytePtr,
bitPtr = this.bitPtr,
result = 0,
bytes = this.bytes;
// keep going until we have no more bits left to peek at
// TODO: Consider putting all bits from bytes we will need into a variable and then
// shifting/masking it to just extract the bits we want.
// This could be considerably faster when reading more than 3 or 4 bits at a time.
while (n > 0) {
if (bytePtr >= bytes.length) {
throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
bytes.length + ", bitPtr=" + bitPtr;
return -1;
}
var numBitsLeftInThisByte = (8 - bitPtr);
if (n >= numBitsLeftInThisByte) {
result <<= numBitsLeftInThisByte;
result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]);
bytePtr++;
bitPtr = 0;
n -= numBitsLeftInThisByte;
} else {
result <<= n;
result |= ((bytes[bytePtr] & (BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr));
bitPtr += n;
n = 0;
}
}
if (movePointers) {
this.bitPtr = bitPtr;
this.bytePtr = bytePtr;
}
return result;
};
/**
* Peek at 16 bits from current position in the buffer.
* Bit at (bytePtr,bitPtr) has the highest position in returning data.
* Taken from getbits.hpp in unrar.
* TODO: Move this out of BitStream and into unrar.
*/
bitjs.io.BitStream.prototype.getBits = function() {
return (((((this.bytes[this.bytePtr] & 0xff) << 16) +
((this.bytes[this.bytePtr + 1] & 0xff) << 8) +
((this.bytes[this.bytePtr + 2] & 0xff))) >>> (8 - this.bitPtr)) & 0xffff);
};
/**
* Reads n bits out of the stream, consuming them (moving the bit pointer).
* @param {number} n The number of bits to read.
* @return {number} The read bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.readBits = function(n) {
return this.peekBits(n, true);
};
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true. Only use this for uncompressed blocks as this throws away remaining
* bits in the current byte.
* @param {number} n The number of bytes to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {Uint8Array} The subarray.
*/
bitjs.io.BitStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return 0;
}
// from http://tools.ietf.org/html/rfc1951#page-11
// "Any bits of input up to the next byte boundary are ignored."
while (this.bitPtr != 0) {
this.readBits(1);
}
var movePointers = movePointers || false;
var bytePtr = this.bytePtr,
bitPtr = this.bitPtr;
var result = this.bytes.subarray(bytePtr, bytePtr + n);
if (movePointers) {
this.bytePtr += n;
}
return result;
};
/**
* @param {number} n The number of bytes to read.
* @return {Uint8Array} The subarray.
*/
bitjs.io.BitStream.prototype.readBytes = function(n) {
return this.peekBytes(n, true);
};
})();

@ -0,0 +1,121 @@
/*
* bytestream.js
*
* Provides a writer for bytes.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
(function() {
/**
* A write-only Byte buffer which uses a Uint8 Typed Array as a backing store.
* @param {number} numBytes The number of bytes to allocate.
* @constructor
*/
bitjs.io.ByteBuffer = function(numBytes) {
if (typeof numBytes != typeof 1 || numBytes <= 0) {
throw "Error! ByteBuffer initialized with '" + numBytes + "'";
}
this.data = new Uint8Array(numBytes);
this.ptr = 0;
};
/**
* @param {number} b The byte to insert.
*/
bitjs.io.ByteBuffer.prototype.insertByte = function(b) {
// TODO: throw if byte is invalid?
this.data[this.ptr++] = b;
};
/**
* @param {Array.<number>|Uint8Array|Int8Array} bytes The bytes to insert.
*/
bitjs.io.ByteBuffer.prototype.insertBytes = function(bytes) {
// TODO: throw if bytes is invalid?
this.data.set(bytes, this.ptr);
this.ptr += bytes.length;
};
/**
* Writes an unsigned number into the next n bytes. If the number is too large
* to fit into n bytes or is negative, an error is thrown.
* @param {number} num The unsigned number to write.
* @param {number} numBytes The number of bytes to write the number into.
*/
bitjs.io.ByteBuffer.prototype.writeNumber = function(num, numBytes) {
if (numBytes < 1) {
throw 'Trying to write into too few bytes: ' + numBytes;
}
if (num < 0) {
throw 'Trying to write a negative number (' + num +
') as an unsigned number to an ArrayBuffer';
}
if (num > (Math.pow(2, numBytes * 8) - 1)) {
throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes';
}
// Roll 8-bits at a time into an array of bytes.
var bytes = [];
while (numBytes-- > 0) {
var eightBits = num & 255;
bytes.push(eightBits);
num >>= 8;
}
this.insertBytes(bytes);
};
/**
* Writes a signed number into the next n bytes. If the number is too large
* to fit into n bytes, an error is thrown.
* @param {number} num The signed number to write.
* @param {number} numBytes The number of bytes to write the number into.
*/
bitjs.io.ByteBuffer.prototype.writeSignedNumber = function(num, numBytes) {
if (numBytes < 1) {
throw 'Trying to write into too few bytes: ' + numBytes;
}
var HALF = Math.pow(2, (numBytes * 8) - 1);
if (num >= HALF || num < -HALF) {
throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes';
}
// Roll 8-bits at a time into an array of bytes.
var bytes = [];
while (numBytes-- > 0) {
var eightBits = num & 255;
bytes.push(eightBits);
num >>= 8;
}
this.insertBytes(bytes);
};
/**
* @param {string} str The ASCII string to write.
*/
bitjs.io.ByteBuffer.prototype.writeASCIIString = function(str) {
for (var i = 0; i < str.length; ++i) {
var curByte = str.charCodeAt(i);
if (curByte < 0 || curByte > 255) {
throw 'Trying to write a non-ASCII string!';
}
this.insertByte(curByte);
}
};
})();

@ -0,0 +1,162 @@
/*
* bytestream.js
*
* Provides readers for byte streams.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
(function() {
/**
* This object allows you to peek and consume bytes as numbers and strings
* out of an ArrayBuffer. In this buffer, everything must be byte-aligned.
*
* @param {ArrayBuffer} ab The ArrayBuffer object.
* @param {number=} opt_offset The offset into the ArrayBuffer
* @param {number=} opt_length The length of this BitStream
* @constructor
*/
bitjs.io.ByteStream = function(ab, opt_offset, opt_length) {
var offset = opt_offset || 0;
var length = opt_length || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length);
this.ptr = 0;
};
/**
* Peeks at the next n bytes as an unsigned number but does not advance the
* pointer
* TODO: This apparently cannot read more than 4 bytes as a number?
* @param {number} n The number of bytes to peek at.
* @return {number} The n bytes interpreted as an unsigned number.
*/
bitjs.io.ByteStream.prototype.peekNumber = function(n) {
// TODO: return error if n would go past the end of the stream?
if (n <= 0 || typeof n != typeof 1)
return -1;
var result = 0;
// read from last byte to first byte and roll them in
var curByte = this.ptr + n - 1;
while (curByte >= this.ptr) {
result <<= 8;
result |= this.bytes[curByte];
--curByte;
}
return result;
};
/**
* Returns the next n bytes as an unsigned number (or -1 on error)
* and advances the stream pointer n bytes.
* @param {number} n The number of bytes to read.
* @return {number} The n bytes interpreted as an unsigned number.
*/
bitjs.io.ByteStream.prototype.readNumber = function(n) {
var num = this.peekNumber(n);
this.ptr += n;
return num;
};
/**
* Returns the next n bytes as a signed number but does not advance the
* pointer.
* @param {number} n The number of bytes to read.
* @return {number} The bytes interpreted as a signed number.
*/
bitjs.io.ByteStream.prototype.peekSignedNumber = function(n) {
var num = this.peekNumber(n);
var HALF = Math.pow(2, (n * 8) - 1);
var FULL = HALF * 2;
if (num >= HALF) num -= FULL;
return num;
};
/**
* Returns the next n bytes as a signed number and advances the stream pointer.
* @param {number} n The number of bytes to read.
* @return {number} The bytes interpreted as a signed number.
*/
bitjs.io.ByteStream.prototype.readSignedNumber = function(n) {
var num = this.peekSignedNumber(n);
this.ptr += n;
return num;
};
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true.
* @param {number} n The number of bytes to read.
* @param {boolean} movePointers Whether to move the pointers.
* @return {Uint8Array} The subarray.
*/
bitjs.io.ByteStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return null;
}
var result = this.bytes.subarray(this.ptr, this.ptr + n);
if (movePointers) {
this.ptr += n;
}
return result;
};
/**
* Reads the next n bytes as a sub-array.
* @param {number} n The number of bytes to read.
* @return {Uint8Array} The subarray.
*/
bitjs.io.ByteStream.prototype.readBytes = function(n) {
return this.peekBytes(n, true);
};
/**
* Peeks at the next n bytes as a string but does not advance the pointer.
* @param {number} n The number of bytes to peek at.
* @return {string} The next n bytes as a string.
*/
bitjs.io.ByteStream.prototype.peekString = function(n) {
if (n <= 0 || typeof n != typeof 1) {
return "";
}
var result = "";
for (var p = this.ptr, end = this.ptr + n; p < end; ++p) {
result += String.fromCharCode(this.bytes[p]);
}
return result;
};
/**
* Returns the next n bytes as an ASCII string and advances the stream pointer
* n bytes.
* @param {number} n The number of bytes to read.
* @return {string} The next n bytes as a string.
*/
bitjs.io.ByteStream.prototype.readString = function(n) {
var strToReturn = this.peekString(n);
this.ptr += n;
return strToReturn;
};
})();

@ -16,7 +16,6 @@
*/ */
/* global screenfull, bitjs */ /* global screenfull, bitjs */
if (window.opera) { if (window.opera) {
window.console.log = function(str) { window.console.log = function(str) {
opera.postError(str); opera.postError(str);
@ -66,7 +65,8 @@ var settings = {
vflip: false, vflip: false,
rotateTimes: 0, rotateTimes: 0,
fitMode: kthoom.Key.B, fitMode: kthoom.Key.B,
theme: "light" theme: "light",
direction: 0 // 0 = Left to Right, 1 = Right to Left
}; };
kthoom.saveSettings = function() { kthoom.saveSettings = function() {
@ -159,11 +159,16 @@ function initProgressClick() {
function loadFromArrayBuffer(ab) { function loadFromArrayBuffer(ab) {
var start = (new Date).getTime(); var start = (new Date).getTime();
var h = new Uint8Array(ab, 0, 10); var h = new Uint8Array(ab, 0, 10);
var pathToBitJS = "../../static/js/"; var pathToBitJS = "../../static/js/archive/";
if (h[0] === 0x52 && h[1] === 0x61 && h[2] === 0x72 && h[3] === 0x21) { //Rar! if (h[0] === 0x52 && h[1] === 0x61 && h[2] === 0x72 && h[3] === 0x21) { //Rar!
unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS); unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS);
} else if (h[0] === 80 && h[1] === 75) { //PK (Zip) } else if (h[0] === 80 && h[1] === 75) { //PK (Zip)
unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS); unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS);
} else if (h[0] == 255 && h[1] == 216) { // JPEG
// ToDo: check
updateProgress(100);
lastCompletion = 100;
return;
} else { // Try with tar } else { // Try with tar
unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS); unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS);
} }
@ -178,6 +183,10 @@ function loadFromArrayBuffer(ab) {
updateProgress(percentage *100); updateProgress(percentage *100);
lastCompletion = percentage * 100; lastCompletion = percentage * 100;
}); });
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.INFO,
function(e) {
// console.log(e.msg); 77 Enable debug output here
});
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT, unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT,
function(e) { function(e) {
// convert DecompressedFile into a bunch of ImageFiles // convert DecompressedFile into a bunch of ImageFiles
@ -367,6 +376,22 @@ function setImage(url) {
} }
} }
function showLeftPage() {
if (settings.direction === 0) {
showPrevPage()
} else {
showNextPage()
}
}
function showRightPage() {
if (settings.direction === 0) {
showNextPage()
} else {
showPrevPage()
}
}
function showPrevPage() { function showPrevPage() {
currentImage--; currentImage--;
if (currentImage < 0) { if (currentImage < 0) {
@ -421,11 +446,11 @@ function keyHandler(evt) {
switch (evt.keyCode) { switch (evt.keyCode) {
case kthoom.Key.LEFT: case kthoom.Key.LEFT:
if (hasModifier) break; if (hasModifier) break;
showPrevPage(); showLeftPage();
break; break;
case kthoom.Key.RIGHT: case kthoom.Key.RIGHT:
if (hasModifier) break; if (hasModifier) break;
showNextPage(); showRightPage();
break; break;
case kthoom.Key.L: case kthoom.Key.L:
if (hasModifier) break; if (hasModifier) break;
@ -486,11 +511,11 @@ function keyHandler(evt) {
if (evt.shiftKey && atTop) { if (evt.shiftKey && atTop) {
evt.preventDefault(); evt.preventDefault();
// If it's Shift + Space and the container is at the top of the page // If it's Shift + Space and the container is at the top of the page
showPrevPage(); showLeftPage();
} else if (!evt.shiftKey && atBottom) { } else if (!evt.shiftKey && atBottom) {
evt.preventDefault(); evt.preventDefault();
// If you're at the bottom of the page and you only pressed space // If you're at the bottom of the page and you only pressed space
showNextPage(); showRightPage();
container.scrollTop(0); container.scrollTop(0);
} }
break; break;
@ -621,25 +646,25 @@ function init(filename) {
// Determine if the user clicked/tapped the left side or the // Determine if the user clicked/tapped the left side or the
// right side of the page. // right side of the page.
var clickedPrev = false; var clickedLeft = false;
switch (settings.rotateTimes) { switch (settings.rotateTimes) {
case 0: case 0:
clickedPrev = clickX < (comicWidth / 2); clickedLeft = clickX < (comicWidth / 2);
break; break;
case 1: case 1:
clickedPrev = clickY < (comicHeight / 2); clickedLeft = clickY < (comicHeight / 2);
break; break;
case 2: case 2:
clickedPrev = clickX > (comicWidth / 2); clickedLeft = clickX > (comicWidth / 2);
break; break;
case 3: case 3:
clickedPrev = clickY > (comicHeight / 2); clickedLeft = clickY > (comicHeight / 2);
break; break;
} }
if (clickedPrev) { if (clickedLeft) {
showPrevPage(); showLeftPage();
} else { } else {
showNextPage(); showRightPage();
} }
}); });
} }

@ -1,7 +1,7 @@
/*! /*!
* screenfull * screenfull
* v3.3.0 - 2017-07-06 * v4.2.0 - 2019-04-01
* (c) Sindre Sorhus; MIT License * (c) Sindre Sorhus; MIT License
*/ */
!function(){"use strict";var a="undefined"!=typeof window&&void 0!==window.document?window.document:{},b="undefined"!=typeof module&&module.exports,c="undefined"!=typeof Element&&"ALLOW_KEYBOARD_INPUT"in Element,d=function(){for(var b,c=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],d=0,e=c.length,f={};d<e;d++)if((b=c[d])&&b[1]in a){for(d=0;d<b.length;d++)f[c[0][d]]=b[d];return f}return!1}(),e={change:d.fullscreenchange,error:d.fullscreenerror},f={request:function(b){var e=d.requestFullscreen;b=b||a.documentElement,/5\.1[.\d]* Safari/.test(navigator.userAgent)?b[e]():b[e](c&&Element.ALLOW_KEYBOARD_INPUT)},exit:function(){a[d.exitFullscreen]()},toggle:function(a){this.isFullscreen?this.exit():this.request(a)},onchange:function(a){this.on("change",a)},onerror:function(a){this.on("error",a)},on:function(b,c){var d=e[b];d&&a.addEventListener(d,c,!1)},off:function(b,c){var d=e[b];d&&a.off(d,c,!1)},raw:d};if(!d)return void(b?module.exports=!1:window.screenfull=!1);Object.defineProperties(f,{isFullscreen:{get:function(){return Boolean(a[d.fullscreenElement])}},element:{enumerable:!0,get:function(){return a[d.fullscreenElement]}},enabled:{enumerable:!0,get:function(){return Boolean(a[d.fullscreenEnabled])}}}),b?module.exports=f:window.screenfull=f}(); !function(){"use strict";var u="undefined"!=typeof window&&void 0!==window.document?window.document:{},e="undefined"!=typeof module&&module.exports,t="undefined"!=typeof Element&&"ALLOW_KEYBOARD_INPUT"in Element,c=function(){for(var e,n=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],l=0,r=n.length,t={};l<r;l++)if((e=n[l])&&e[1]in u){for(l=0;l<e.length;l++)t[n[0][l]]=e[l];return t}return!1}(),r={change:c.fullscreenchange,error:c.fullscreenerror},n={request:function(r){return new Promise(function(e){var n=c.requestFullscreen,l=function(){this.off("change",l),e()}.bind(this);r=r||u.documentElement,/ Version\/5\.1(?:\.\d+)? Safari\//.test(navigator.userAgent)?r[n]():r[n](t?Element.ALLOW_KEYBOARD_INPUT:{}),this.on("change",l)}.bind(this))},exit:function(){return new Promise(function(e){if(this.isFullscreen){var n=function(){this.off("change",n),e()}.bind(this);u[c.exitFullscreen](),this.on("change",n)}else e()}.bind(this))},toggle:function(e){return this.isFullscreen?this.exit():this.request(e)},onchange:function(e){this.on("change",e)},onerror:function(e){this.on("error",e)},on:function(e,n){var l=r[e];l&&u.addEventListener(l,n,!1)},off:function(e,n){var l=r[e];l&&u.removeEventListener(l,n,!1)},raw:c};c?(Object.defineProperties(n,{isFullscreen:{get:function(){return Boolean(u[c.fullscreenElement])}},element:{enumerable:!0,get:function(){return u[c.fullscreenElement]}},enabled:{enumerable:!0,get:function(){return Boolean(u[c.fullscreenEnabled])}}}),e?(module.exports=n,module.exports.default=n):window.screenfull=n):e?module.exports=!1:window.screenfull=!1}();

@ -1,209 +0,0 @@
/**
* untar.js
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
*
* Reference Documentation:
*
* TAR format: http://www.gnu.org/software/automake/manual/tar/Standard.html
*/
// This file expects to be invoked as a Worker (see onmessage below).
importScripts('bytestream.js');
importScripts('archive.js');
const UnarchiveState = {
NOT_STARTED: 0,
UNARCHIVING: 1,
WAITING: 2,
FINISHED: 3,
};
// State - consider putting these into a class.
let unarchiveState = UnarchiveState.NOT_STARTED;
let bytestream = null;
let allLocalFiles = null;
let logToConsole = false;
// Progress variables.
let currentFilename = "";
let currentFileNumber = 0;
let currentBytesUnarchivedInFile = 0;
let currentBytesUnarchived = 0;
let totalUncompressedBytesInArchive = 0;
let totalFilesInArchive = 0;
// Helper functions.
const info = function(str) {
postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
};
const err = function(str) {
postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
};
// Removes all characters from the first zero-byte in the string onwards.
var readCleanString = function(bstr, numBytes) {
var str = bstr.readString(numBytes);
var zIndex = str.indexOf(String.fromCharCode(0));
return zIndex != -1 ? str.substr(0, zIndex) : str;
};
const postProgress = function() {
postMessage(new bitjs.archive.UnarchiveProgressEvent(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive,
bytestream.getNumBytesRead(),
));
};
class TarLocalFile {
// takes a ByteStream and parses out the local file information
constructor(bstream) {
this.isValid = false;
let bytesRead = 0;
// Read in the header block
this.name = readCleanString(bstream, 100);
this.mode = readCleanString(bstream, 8);
this.uid = readCleanString(bstream, 8);
this.gid = readCleanString(bstream, 8);
this.size = parseInt(readCleanString(bstream, 12), 8);
this.mtime = readCleanString(bstream, 12);
this.chksum = readCleanString(bstream, 8);
this.typeflag = readCleanString(bstream, 1);
this.linkname = readCleanString(bstream, 100);
this.maybeMagic = readCleanString(bstream, 6);
if (this.maybeMagic == "ustar") {
this.version = readCleanString(bstream, 2);
this.uname = readCleanString(bstream, 32);
this.gname = readCleanString(bstream, 32);
this.devmajor = readCleanString(bstream, 8);
this.devminor = readCleanString(bstream, 8);
this.prefix = readCleanString(bstream, 155);
if (this.prefix.length) {
this.name = this.prefix + this.name;
}
bstream.readBytes(12); // 512 - 500in
} else {
bstream.readBytes(255); // 512 - 257
}
bytesRead += 512;
// Done header, now rest of blocks are the file contents.
this.filename = this.name;
this.fileData = null;
info("Untarring file '" + this.filename + "'");
info(" size = " + this.size);
info(" typeflag = " + this.typeflag);
// A regular file.
if (this.typeflag == 0) {
info(" This is a regular file.");
const sizeInBytes = parseInt(this.size);
this.fileData = new Uint8Array(bstream.readBytes(sizeInBytes));
bytesRead += sizeInBytes;
if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) {
this.isValid = true;
}
// Round up to 512-byte blocks.
const remaining = 512 - bytesRead % 512;
if (remaining > 0 && remaining < 512) {
bstream.readBytes(remaining);
}
} else if (this.typeflag == 5) {
info(" This is a directory.")
}
}
}
const untar = function() {
let bstream = bytestream.tee();
// While we don't encounter an empty block, keep making TarLocalFiles.
while (bstream.peekNumber(4) != 0) {
const oneLocalFile = new TarLocalFile(bstream);
if (oneLocalFile && oneLocalFile.isValid) {
// If we make it to this point and haven't thrown an error, we have successfully
// read in the data for a local file, so we can update the actual bytestream.
bytestream = bstream.tee();
allLocalFiles.push(oneLocalFile);
totalUncompressedBytesInArchive += oneLocalFile.size;
// update progress
currentFilename = oneLocalFile.filename;
currentFileNumber = totalFilesInArchive++;
currentBytesUnarchivedInFile = oneLocalFile.size;
currentBytesUnarchived += oneLocalFile.size;
postMessage(new bitjs.archive.UnarchiveExtractEvent(oneLocalFile));
postProgress();
}
}
totalFilesInArchive = allLocalFiles.length;
postProgress();
bytestream = bstream.tee();
};
// event.data.file has the first ArrayBuffer.
// event.data.bytes has all subsequent ArrayBuffers.
onmessage = function(event) {
const bytes = event.data.file || event.data.bytes;
logToConsole = !!event.data.logToConsole;
// This is the very first time we have been called. Initialize the bytestream.
if (!bytestream) {
bytestream = new bitjs.io.ByteStream(bytes);
} else {
bytestream.push(bytes);
}
if (unarchiveState === UnarchiveState.NOT_STARTED) {
currentFilename = "";
currentFileNumber = 0;
currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0;
allLocalFiles = [];
postMessage(new bitjs.archive.UnarchiveStartEvent());
unarchiveState = UnarchiveState.UNARCHIVING;
postProgress();
}
if (unarchiveState === UnarchiveState.UNARCHIVING ||
unarchiveState === UnarchiveState.WAITING) {
try {
untar();
unarchiveState = UnarchiveState.FINISHED;
postMessage(new bitjs.archive.UnarchiveFinishEvent());
} catch (e) {
if (typeof e === 'string' && e.startsWith('Error! Overflowed')) {
// Overrun the buffer.
unarchiveState = UnarchiveState.WAITING;
} else {
console.error('Found an error while untarring');
console.dir(e);
throw e;
}
}
}
};

@ -146,7 +146,7 @@
<div class="modal-body text-center"> <div class="modal-body text-center">
<p>{{_('Do you really want to restart Calibre-Web?')}}</p> <p>{{_('Do you really want to restart Calibre-Web?')}}</p>
<div id="spinner" class="spinner" style="display:none;"> <div id="spinner" class="spinner" style="display:none;">
<img id="img-spinner" src="{{ url_for('static', filename='css/images/loading-icon.gif') }}"/> <img id="img-spinner" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/>
</div> </div>
<p></p> <p></p>
<button type="button" class="btn btn-default" id="restart" >{{_('Ok')}}</button> <button type="button" class="btn btn-default" id="restart" >{{_('Ok')}}</button>
@ -178,7 +178,7 @@
</div> </div>
<div class="modal-body text-center"> <div class="modal-body text-center">
<div id="spinner2" class="spinner2" style="display:none;"> <div id="spinner2" class="spinner2" style="display:none;">
<img id="img-spinner2" src="{{ url_for('static', filename='css/images/loading-icon.gif') }}"/> <img id="img-spinner2" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/>
</div> </div>
<p></p> <p></p>
<div id="Updatecontent"></div> <div id="Updatecontent"></div>

@ -170,9 +170,9 @@
{% endif %} {% endif %}
{% if cc|length > 0 %} {% if cc|length > 0 %}
<div class="custom_columns">
<p>
{% for c in cc %} {% for c in cc %}
<div class="real_custom_columns">
{% if entry['custom_column_' ~ c.id]|length > 0 %} {% if entry['custom_column_' ~ c.id]|length > 0 %}
{{ c.name }}: {{ c.name }}:
{% for column in entry['custom_column_' ~ c.id] %} {% for column in entry['custom_column_' ~ c.id] %}
@ -190,11 +190,9 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<br />
{% endif %} {% endif %}
{% endfor %}
</p>
</div> </div>
{% endfor %}
{% endif %} {% endif %}
{% if not g.user.is_anonymous %} {% if not g.user.is_anonymous %}
@ -213,7 +211,7 @@
{% if entry.comments|length > 0 and entry.comments[0].text|length > 0%} {% if entry.comments|length > 0 and entry.comments[0].text|length > 0%}
<div class="comments"> <div class="comments">
<h3>{{_('Description:')}}</h3> <h3 id="decription">{{_('Description:')}}</h3>
{{entry.comments[0].text|safe}} {{entry.comments[0].text|safe}}
</div> </div>
{% endif %} {% endif %}

@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Comic Reader</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Comic Reader</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
@ -15,23 +15,27 @@
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/kthoom.js') }}"></script> <script src="{{ url_for('static', filename='js/kthoom.js') }}"></script>
<script src="{{ url_for('static', filename='js/archive.js') }}"></script> <script src="{{ url_for('static', filename='js/archive/archive.js') }}"></script>
<script> <script>
var updateArrows = function() {
if ($('input[name="direction"]:checked').val() === "0") {
$("#prev_page_key").html("&larr;");
$("#next_page_key").html("&rarr;");
} else {
$("#prev_page_key").html("&rarr;");
$("#next_page_key").html("&larr;");
}
};
document.onreadystatechange = function () { document.onreadystatechange = function () {
if (document.readyState == "complete") { if (document.readyState == "complete") {
init("{{ url_for('web.serve_book', book_id=comicfile, book_format=extension) }}"); init("{{ url_for('web.serve_book', book_id=comicfile, book_format=extension) }}");
updateArrows();
} }
}; }
</script> </script>
</head> </head>
<body> <body>
<div id="sidebar"> <div id="sidebar">
<!--
<div id="panels">
<a id="show-Toc" class="show_view icon-list-1 active" data-view="Toc">TOC</a>
<a id="show-Bookmarks" class="show_view icon-bookmark" data-view="Bookmarks">Bookmarks</a>
</div>
-->
<div id="tocView" class="view" tabindex="-1"> <div id="tocView" class="view" tabindex="-1">
<ul id="thumbnails"></ul> <ul id="thumbnails"></ul>
</div> </div>
@ -70,8 +74,8 @@
<div id="mainText" style="display:none"></div> <div id="mainText" style="display:none"></div>
<canvas id="mainImage"></canvas> <canvas id="mainImage"></canvas>
</div> </div>
<div id="prev" class="arrow" onclick="showPrevPage()"></div> <div id="left" class="arrow" onclick="showLeftPage()"></div>
<div id="next" class="arrow" onclick="showNextPage()"></div> <div id="right" class="arrow" onclick="showRightPage()"></div>
</div> </div>
<div class="modal md-effect-1" id="settings-modal"> <div class="modal md-effect-1" id="settings-modal">
@ -84,8 +88,8 @@
<tr><th colspan="2">{{_('Keyboard Shortcuts')}}</th></tr> <tr><th colspan="2">{{_('Keyboard Shortcuts')}}</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>&larr;</td> <td>{{_('Previous Page')}}</td></tr> <tr><td id="prev_page_key">&larr;</td> <td>{{_('Previous Page')}}</td></tr>
<tr><td>&rarr;</td> <td>{{_('Next Page')}}</td></tr> <tr><td id="next_page_key">&rarr;</td> <td>{{_('Next Page')}}</td></tr>
<tr><td>B</td> <td>{{_('Scale to Best')}}</td></tr> <tr><td>B</td> <td>{{_('Scale to Best')}}</td></tr>
<tr><td>W</td> <td>{{_('Scale to Width')}}</td></tr> <tr><td>W</td> <td>{{_('Scale to Width')}}</td></tr>
<tr><td>H</td> <td>{{_('Scale to Height')}}</td></tr> <tr><td>H</td> <td>{{_('Scale to Height')}}</td></tr>
@ -144,6 +148,15 @@
</div> </div>
</td> </td>
</tr> </tr>
<tr>
<th>{{_('Direction')}}:</th>
<td>
<div class="inputs">
<label for="leftToRight"><input type="radio" id="leftToRight" name="direction" value="0" /> {{_('Left to Right')}}</label>
<label for="rightToLeft"><input type="radio" id="rightToLeft" name="direction" value="1" /> {{_('Right to Left')}}</label>
</div>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -152,6 +165,10 @@
</div> </div>
</div> </div>
<div class="overlay"></div> <div class="overlay"></div>
<script>
$('input[name="direction"]').change(function() {
updateArrows();
});
</script>
</body> </body>
</html> </html>

@ -34,20 +34,17 @@ See https://github.com/adobe-type-tools/cmap-resources
<script src="{{ url_for('static', filename='js/libs/compatibility.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/compatibility.js') }}"></script>
<!-- This snippet is used in production (included from viewer.html) --> <!-- This snippet is used in production (included from viewer.html) -->
<link rel="resource" type="application/l10n" href="{{ url_for('static', filename='locale/locale.properties') }}">
<script src="{{ url_for('static', filename='js/libs/pdf.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/pdf.js') }}"></script>
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('load', function() { window.addEventListener('load', function() {
PDFViewerApplicationOptions.set('sidebarViewOnLoad', 0);
PDFViewerApplicationOptions.set('imageResourcesPath', "{{ url_for('static', filename='css/images/') }}"); PDFViewerApplicationOptions.set('imageResourcesPath', "{{ url_for('static', filename='css/images/') }}");
PDFViewerApplicationOptions.set('workerSrc', "{{ url_for('static', filename='js/libs/pdf.worker.js') }}"); PDFViewerApplicationOptions.set('workerSrc', "{{ url_for('static', filename='js/libs/pdf.worker.js') }}");
PDFViewerApplicationOptions.set('sidebarViewOnLoad', 0); PDFViewerApplication.open("{{ url_for('serve_book', book_id=pdffile, book_format='pdf') }}");
// PDFViewerApplication.appConfig.secondaryToolbar.downloadButton.setAttribute('hidden', 'true');
// PDFViewerApplication.open("{{ url_for('web.serve_book', book_id=pdffile, book_format='pdf') }}");
PDFViewerApplicationOptions.set('defaultUrl', "{{ url_for('web.serve_book', book_id=pdffile, book_format='pdf') }}");
}); });
</script> </script>
<link rel="resource" type="application/l10n" href="{{ url_for('static', filename='locale/locale.properties') }}">
<script src="{{ url_for('static', filename='js/libs/viewer.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/viewer.js') }}"></script>
</head> </head>

@ -3,13 +3,13 @@
<div class="well"> <div class="well">
<h2 style="margin-top: 0">{{_('Remote login')}}</h2> <h2 style="margin-top: 0">{{_('Remote login')}}</h2>
<p> <p>
{{_('Using your another device, visit')}} <a href="{{verify_url}}">{{verify_url}}</a> {{_('and log in')}}. {{_('Use your other device, login and visit ')}} <a href="{{verify_url}}">{{verify_url}}</a>.
</p> </p>
<p> <p>
{{_('Once you do so, you will automatically get logged in on this device.')}} {{_('Once you do so, you will automatically get logged in on this device.')}}
</p> </p>
<p> <p>
{{_('The link will expire after %s minutes.' % 10)}} {{_('The link will expire after 10 minutes.')}}
</p> </p>
</div> </div>
{% endblock %} {% endblock %}

@ -496,19 +496,31 @@ msgstr ""
msgid "Not linked to %(oauth)s." msgid "Not linked to %(oauth)s."
msgstr "" msgstr ""
#: cps/oauth_bb.py:300 msgstr "Erfolg! Bitte zum Gerät zurückkehren"
msgid "GitHub Oauth error, please retry later."
msgstr ""
#: cps/oauth_bb.py:319 #: cps/web.py:2407
msgid "Google Oauth error, please retry later." msgid "Please configure the SMTP mail settings first..."
msgstr "" msgstr "Bitte zuerst die SMTP Mail Einstellung konfigurieren ..."
#: cps/shelf.py:40 cps/shelf.py:92 #: cps/web.py:2412
#, python-format
msgid "Book successfully queued for sending to %(kindlemail)s"
msgstr "Buch erfolgreich zum Senden an %(kindlemail)s eingereiht"
#: cps/web.py:2416
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr "Beim Senden des Buchs trat ein Fehler auf: %(res)s"
#: cps/web.py:2418 cps/web.py:3223
msgid "Please configure your kindle e-mail address first..."
msgstr "Bitte zuerst die Kindle E-Mailadresse konfigurieren..."
#: cps/web.py:2429 cps/web.py:2481
msgid "Invalid shelf specified" msgid "Invalid shelf specified"
msgstr "Ungültiges Bücherregal angegeben" msgstr "Ungültiges Bücherregal angegeben"
#: cps/shelf.py:47 #: cps/web.py:2436
#, python-format #, python-format
msgid "Sorry you are not allowed to add a book to the the shelf: %(shelfname)s" msgid "Sorry you are not allowed to add a book to the the shelf: %(shelfname)s"
msgstr "Keine Erlaubnis ein Buch zum Bücherregale %(shelfname)s hinzuzufügen vorhanden" msgstr "Keine Erlaubnis ein Buch zum Bücherregale %(shelfname)s hinzuzufügen vorhanden"
@ -1511,7 +1523,7 @@ msgstr "Anzahl Anzeige zufällige Bücher"
msgid "No. of authors to show before hiding (0=disable hiding)" msgid "No. of authors to show before hiding (0=disable hiding)"
msgstr "Anzahl Autoren in Übersicht (0=deaktiviert)" msgstr "Anzahl Autoren in Übersicht (0=deaktiviert)"
#: cps/templates/config_view_edit.html:35 cps/templates/readcbr.html:108 #: cps/templates/config_view_edit.html:35 cps/templates/readcbr.html:118
msgid "Theme" msgid "Theme"
msgstr "Theme" msgstr "Theme"
@ -1773,7 +1785,7 @@ msgid "Advanced Search"
msgstr "Erweiterte Suche" msgstr "Erweiterte Suche"
#: cps/templates/layout.html:76 cps/templates/read.html:71 #: cps/templates/layout.html:76 cps/templates/read.html:71
#: cps/templates/readcbr.html:79 cps/templates/readcbr.html:103 #: cps/templates/readcbr.html:89 cps/templates/readcbr.html:113
msgid "Settings" msgid "Settings"
msgstr "Einstellungen" msgstr "Einstellungen"
@ -1851,98 +1863,106 @@ msgstr "Calibre-Web E-Book Katalog"
msgid "Reflow text when sidebars are open." msgid "Reflow text when sidebars are open."
msgstr "Text umbrechen wenn Seitenleiste geöffnet ist." msgstr "Text umbrechen wenn Seitenleiste geöffnet ist."
#: cps/templates/readcbr.html:84 #: cps/templates/readcbr.html:94
msgid "Keyboard Shortcuts" msgid "Keyboard Shortcuts"
msgstr "Tastatur Kürzel" msgstr "Tastatur Kürzel"
#: cps/templates/readcbr.html:87 #: cps/templates/readcbr.html:97
msgid "Previous Page" msgid "Previous Page"
msgstr "Vorherige Seite" msgstr "Vorherige Seite"
#: cps/templates/readcbr.html:88 #: cps/templates/readcbr.html:98
msgid "Next Page" msgid "Next Page"
msgstr "Nächste Seite" msgstr "Nächste Seite"
#: cps/templates/readcbr.html:89 #: cps/templates/readcbr.html:99
msgid "Scale to Best" msgid "Scale to Best"
msgstr "Optimale Skalierung" msgstr "Optimale Skalierung"
#: cps/templates/readcbr.html:90 #: cps/templates/readcbr.html:100
msgid "Scale to Width" msgid "Scale to Width"
msgstr "Skaliere auf Breite" msgstr "Skaliere auf Breite"
#: cps/templates/readcbr.html:91 #: cps/templates/readcbr.html:101
msgid "Scale to Height" msgid "Scale to Height"
msgstr "Skaliere auf Höhe" msgstr "Skaliere auf Höhe"
#: cps/templates/readcbr.html:92 #: cps/templates/readcbr.html:102
msgid "Scale to Native" msgid "Scale to Native"
msgstr "Skaliere 1:1" msgstr "Skaliere 1:1"
#: cps/templates/readcbr.html:93 #: cps/templates/readcbr.html:103
msgid "Rotate Right" msgid "Rotate Right"
msgstr "Rechts rotieren" msgstr "Rechts rotieren"
#: cps/templates/readcbr.html:94 #: cps/templates/readcbr.html:104
msgid "Rotate Left" msgid "Rotate Left"
msgstr "Links rotieren" msgstr "Links rotieren"
#: cps/templates/readcbr.html:95 #: cps/templates/readcbr.html:105
msgid "Flip Image" msgid "Flip Image"
msgstr "Bild umdrehen" msgstr "Bild umdrehen"
#: cps/templates/readcbr.html:111 #: cps/templates/readcbr.html:121
msgid "Light" msgid "Light"
msgstr "Hell" msgstr "Hell"
#: cps/templates/readcbr.html:112 #: cps/templates/readcbr.html:122
msgid "Dark" msgid "Dark"
msgstr "Dunkel" msgstr "Dunkel"
#: cps/templates/readcbr.html:117 #: cps/templates/readcbr.html:127
msgid "Scale" msgid "Scale"
msgstr "Skalierung" msgstr "Skalierung"
#: cps/templates/readcbr.html:120 #: cps/templates/readcbr.html:130
msgid "Best" msgid "Best"
msgstr "Beste" msgstr "Beste"
#: cps/templates/readcbr.html:121 #: cps/templates/readcbr.html:131
msgid "Width" msgid "Width"
msgstr "Breite" msgstr "Breite"
#: cps/templates/readcbr.html:122 #: cps/templates/readcbr.html:132
msgid "Height" msgid "Height"
msgstr "Höhe" msgstr "Höhe"
#: cps/templates/readcbr.html:123 #: cps/templates/readcbr.html:133
msgid "Native" msgid "Native"
msgstr "1:1" msgstr "1:1"
#: cps/templates/readcbr.html:128 #: cps/templates/readcbr.html:138
msgid "Rotate" msgid "Rotate"
msgstr "Rotieren" msgstr "Rotieren"
#: cps/templates/readcbr.html:139 #: cps/templates/readcbr.html:149
msgid "Flip" msgid "Flip"
msgstr "Umdrehen" msgstr "Umdrehen"
#: cps/templates/readcbr.html:142 #: cps/templates/readcbr.html:152
msgid "Horizontal" msgid "Horizontal"
msgstr "Horizontal" msgstr "Horizontal"
#: cps/templates/readcbr.html:143 #: cps/templates/readcbr.html:153
msgid "Vertical" msgid "Vertical"
msgstr "Vertikal" msgstr "Vertikal"
#: cps/templates/readcbr.html:158
msgid "Direction"
msgstr "Leserichtung"
#: cps/templates/readcbr.html:161
msgid "Left to Right"
msgstr "Links nach rechts"
#: cps/templates/readcbr.html:162
msgid "Right to Left"
msgstr "Rechts nach links"
#: cps/templates/readpdf.html:29 #: cps/templates/readpdf.html:29
msgid "PDF.js viewer" msgid "PDF.js viewer"
msgstr "PDF.js Viewer" msgstr "PDF.js Viewer"
#: cps/templates/readpdf.html:418
msgid "Preparing document for printing..."
msgstr "Bereite Dokument zum Ducken vor..."
#: cps/templates/readtxt.html:6 #: cps/templates/readtxt.html:6
msgid "Basic txt Reader" msgid "Basic txt Reader"
msgstr "Basis Txt Reader" msgstr "Basis Txt Reader"
@ -1964,17 +1984,17 @@ msgid "Your email address"
msgstr "Deine E-Mail Adresse" msgstr "Deine E-Mail Adresse"
#: cps/templates/remote_login.html:6 #: cps/templates/remote_login.html:6
msgid "Using your another device, visit" msgid "Use your other device, login and visit "
msgstr "Benutze das andere Gerät und besuche" msgstr "Benutze dein anderes Gerät, logge dich ein und besuche "
#: cps/templates/remote_login.html:6
msgid "and log in"
msgstr "und logge Dich ein"
#: cps/templates/remote_login.html:9 #: cps/templates/remote_login.html:9
msgid "Once you do so, you will automatically get logged in on this device." msgid "Once you do so, you will automatically get logged in on this device."
msgstr "Danach wirst Du automatisch auf diesem Gerät eingeloggt sein." msgstr "Danach wirst Du automatisch auf diesem Gerät eingeloggt sein."
#: cps/templates/remote_login.html:12
msgid "The link will expire after 10 minutes."
msgstr "Dieser Link wird in 10 Minuten ablaufen."
#: cps/templates/search.html:5 #: cps/templates/search.html:5
msgid "No Results for:" msgid "No Results for:"
msgstr "Keine Ergebnisse für:" msgstr "Keine Ergebnisse für:"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -435,7 +435,7 @@ class RemoteAuthToken(Base):
expiration = Column(DateTime) expiration = Column(DateTime)
def __init__(self): def __init__(self):
self.auth_token = hexlify(os.urandom(4)) self.auth_token = (hexlify(os.urandom(4))).decode('utf-8')
self.expiration = datetime.datetime.now() + datetime.timedelta(minutes=10) # 10 min from now self.expiration = datetime.datetime.now() + datetime.timedelta(minutes=10) # 10 min from now
def __repr__(self): def __repr__(self):

@ -243,7 +243,7 @@ class Updater(threading.Thread):
@classmethod @classmethod
def _stable_version_info(self): def _stable_version_info(self):
return {'version': '0.6.3 Beta'} # Current version return {'version': '0.6.4 Beta'} # Current version
def _nightly_available_updates(self, request_method): def _nightly_available_updates(self, request_method):
tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone) tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)

@ -23,7 +23,7 @@ import hashlib
from collections import namedtuple from collections import namedtuple
import os import os
from flask_babel import gettext as _ from flask_babel import gettext as _
import comic from cps import comic
from cps import app from cps import app
try: try:

@ -1294,7 +1294,7 @@ def read_book(book_id, book_format):
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect(url_for("web.index")) return redirect(url_for("web.index"))
# check if book was downloaded before # check if book has bookmark
bookmark = None bookmark = None
if current_user.is_authenticated: if current_user.is_authenticated:
bookmark = ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id), bookmark = ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),

@ -19,9 +19,12 @@ python-Levenshtein>=0.12.0
# ldap login # ldap login
python_ldap>=3.0.0 python_ldap>=3.0.0
# other # extracting metadata
lxml>=3.8.0 lxml>=3.8.0
Pillow>=4.0.0
rarfile>=2.7 rarfile>=2.7
# other
natsort>=2.2.0 natsort>=2.2.0
# Oauth Login # Oauth Login

@ -13,4 +13,4 @@ SQLAlchemy>=1.1.0
tornado>=4.1 tornado>=4.1
Wand>=0.4.4 Wand>=0.4.4
unidecode>=0.04.19 unidecode>=0.04.19
Pillow>=5.4.0 git+https://github.com/wildthyme/comicapi.git@cb279168f9c5cec742b5a05ac8326b9c168a8a91#egg=comicapi

Loading…
Cancel
Save