Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aba88ae53a | ||
|
|
67d0ddb180 | ||
|
|
b1bb1cfdfa | ||
|
|
37736e11d5 | ||
|
|
792367e35e | ||
|
|
b1cb7123a3 | ||
|
|
f79d549910 | ||
|
|
66283c542f | ||
|
|
c1d5f77fe8 | ||
|
|
d9f69ca264 | ||
|
|
cd546eb6d4 | ||
|
|
e67d707867 | ||
|
|
3f5c6c1fa5 | ||
|
|
f5e3ed26b9 | ||
|
|
c81d4edb7d | ||
|
|
f736a15c12 | ||
|
|
87ca05f129 | ||
|
|
b3a286c0b5 | ||
|
|
26e45f1f57 | ||
|
|
df5d15d1a2 | ||
|
|
ecedf92783 | ||
|
|
867aa2f0bd | ||
|
|
7982ed877c | ||
|
|
c0d136ccd8 | ||
|
|
029d299067 | ||
|
|
b7e30644ab | ||
|
|
cbdc9876b2 | ||
|
|
05d0f12608 |
@@ -0,0 +1,3 @@
|
||||
include cps/static/*
|
||||
include cps/templates/*
|
||||
include cps/translations/*
|
||||
Regular → Executable
+75
-25
@@ -17,33 +17,52 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import zipfile
|
||||
import tarfile
|
||||
import os
|
||||
import uploader
|
||||
import logging
|
||||
from iso639 import languages as isoLanguages
|
||||
|
||||
|
||||
logger = logging.getLogger("book_formats")
|
||||
|
||||
try:
|
||||
from comicapi.comicarchive import ComicArchive, MetaDataStyle
|
||||
use_comic_meta = True
|
||||
except ImportError as e:
|
||||
logger.warning('cannot import comicapi, extracting comic metadata will not work: %s', e)
|
||||
import zipfile
|
||||
import tarfile
|
||||
use_comic_meta = False
|
||||
|
||||
|
||||
def extractCover(tmp_file_name, original_file_extension):
|
||||
cover_data = None
|
||||
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
|
||||
|
||||
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)
|
||||
if cover_data:
|
||||
tmp_cover_name = prefix + '/cover' + extension
|
||||
@@ -56,15 +75,46 @@ def extractCover(tmp_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)
|
||||
|
||||
return uploader.BookMeta(
|
||||
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(
|
||||
file_path=tmp_file_path,
|
||||
extension=original_file_extension,
|
||||
title=original_file_name,
|
||||
author=u"Unknown",
|
||||
cover=coverfile,
|
||||
cover=extractCover(tmp_file_path, original_file_extension),
|
||||
description="",
|
||||
tags="",
|
||||
series="",
|
||||
|
||||
+32
-1
@@ -34,7 +34,8 @@ from flask import send_from_directory, make_response, redirect, abort
|
||||
from flask_babel import gettext as _
|
||||
from flask_login import current_user
|
||||
from babel.dates import format_datetime
|
||||
from datetime import datetime
|
||||
from babel.units import format_unit
|
||||
from datetime import datetime, timedelta
|
||||
import shutil
|
||||
import requests
|
||||
try:
|
||||
@@ -566,8 +567,33 @@ def json_serial(obj):
|
||||
|
||||
if isinstance(obj, (datetime)):
|
||||
return obj.isoformat()
|
||||
if isinstance(obj, (timedelta)):
|
||||
return {
|
||||
'__type__': 'timedelta',
|
||||
'days': obj.days,
|
||||
'seconds': obj.seconds,
|
||||
'microseconds': obj.microseconds,
|
||||
}
|
||||
raise TypeError ("Type %s not serializable" % type(obj))
|
||||
|
||||
|
||||
# helper function for displaying the runtime of tasks
|
||||
def format_runtime(runtime):
|
||||
retVal = ""
|
||||
if runtime.days:
|
||||
retVal = format_unit(runtime.days, 'duration-day', length="long", locale=web.get_locale()) + ', '
|
||||
mins, seconds = divmod(runtime.seconds, 60)
|
||||
hours, minutes = divmod(mins, 60)
|
||||
# ToDo: locale.number_symbols._data['timeSeparator'] -> localize time separator ?
|
||||
if hours:
|
||||
retVal += '{:d}:{:02d}:{:02d}s'.format(hours, minutes, seconds)
|
||||
elif minutes:
|
||||
retVal += '{:2d}:{:02d}s'.format(minutes, seconds)
|
||||
else:
|
||||
retVal += '{:2d}s'.format(seconds)
|
||||
return retVal
|
||||
|
||||
|
||||
# helper function to apply localize status information in tasklist entries
|
||||
def render_task_status(tasklist):
|
||||
renderedtasklist=list()
|
||||
@@ -579,6 +605,11 @@ def render_task_status(tasklist):
|
||||
if 'starttime' not in task:
|
||||
task['starttime'] = ""
|
||||
|
||||
if 'formRuntime' not in task:
|
||||
task['runtime'] = ""
|
||||
else:
|
||||
task['runtime'] = format_runtime(task['formRuntime'])
|
||||
|
||||
# localize the task status
|
||||
if isinstance( task['stat'], int ):
|
||||
if task['stat'] == worker.STAT_WAITING:
|
||||
|
||||
+5
-4
@@ -103,6 +103,7 @@ class server:
|
||||
ssl_options=ssl)
|
||||
http_server.listen(web.ub.config.config_port)
|
||||
self.wsgiserver=IOLoop.instance()
|
||||
web.py3_gevent_link = self.wsgiserver
|
||||
self.wsgiserver.start()
|
||||
# wait for stop signal
|
||||
self.wsgiserver.close(True)
|
||||
@@ -142,10 +143,10 @@ class server:
|
||||
# ToDo: Somehow caused by circular import under python3 refactor
|
||||
if sys.version_info > (3, 0):
|
||||
if not self.wsgiserver:
|
||||
if gevent_present:
|
||||
self.wsgiserver = web.py3_gevent_link
|
||||
else:
|
||||
self.wsgiserver = IOLoop.instance()
|
||||
# if gevent_present:
|
||||
self.wsgiserver = web.py3_gevent_link
|
||||
#else:
|
||||
# self.wsgiserver = IOLoop.instance()
|
||||
if self.wsgiserver:
|
||||
if gevent_present:
|
||||
self.wsgiserver.close()
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
@@ -1,3 +1,67 @@
|
||||
/* alphanum.js (C) Brian Huisman
|
||||
* Based on the Alphanum Algorithm by David Koelle
|
||||
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
|
||||
*
|
||||
* Distributed under same license as original
|
||||
*
|
||||
* Released under the MIT License - https://opensource.org/licenses/MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
/* ********************************************************************
|
||||
* Alphanum sort() function version - case insensitive
|
||||
* - Slower, but easier to modify for arrays of objects which contain
|
||||
* string properties
|
||||
*
|
||||
*/
|
||||
function alphanumCase(a, b) {
|
||||
function chunkify(t) {
|
||||
var tz = new Array();
|
||||
var x = 0, y = -1, n = 0, i, j;
|
||||
|
||||
while (i = (j = t.charAt(x++)).charCodeAt(0)) {
|
||||
var m = (i == 46 || (i >=48 && i <= 57));
|
||||
if (m !== n) {
|
||||
tz[++y] = "";
|
||||
n = m;
|
||||
}
|
||||
tz[y] += j;
|
||||
}
|
||||
return tz;
|
||||
}
|
||||
|
||||
var aa = chunkify(a.filename.toLowerCase());
|
||||
var bb = chunkify(b.filename.toLowerCase());
|
||||
|
||||
for (x = 0; aa[x] && bb[x]; x++) {
|
||||
if (aa[x] !== bb[x]) {
|
||||
var c = Number(aa[x]), d = Number(bb[x]);
|
||||
if (c == aa[x] && d == bb[x]) {
|
||||
return c - d;
|
||||
} else return (aa[x] > bb[x]) ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return aa.length - bb.length;
|
||||
}
|
||||
// ===========================================================================
|
||||
|
||||
|
||||
/**
|
||||
* archive.js
|
||||
*
|
||||
@@ -8,7 +72,7 @@
|
||||
* Copyright(c) 2011 Google Inc.
|
||||
*/
|
||||
|
||||
/* global bitjs */
|
||||
/* global bitjs, Uint8Array */
|
||||
|
||||
var bitjs = bitjs || {};
|
||||
bitjs.archive = bitjs.archive || {};
|
||||
@@ -27,8 +91,7 @@ bitjs.archive = bitjs.archive || {};
|
||||
|
||||
var args = Array.prototype.slice.call(arguments, 2);
|
||||
var foundCaller = false;
|
||||
for (var ctor = me.constructor;
|
||||
ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
|
||||
for (var ctor = me.constructor; ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
|
||||
if (ctor.prototype[optMethodName] === caller) {
|
||||
foundCaller = true;
|
||||
} else if (foundCaller) {
|
||||
@@ -66,10 +129,10 @@ bitjs.archive = bitjs.archive || {};
|
||||
*/
|
||||
bitjs.archive.UnarchiveEvent = function(type) {
|
||||
/**
|
||||
* The event type.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
* The event type.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = type;
|
||||
};
|
||||
|
||||
@@ -94,10 +157,10 @@ bitjs.archive = bitjs.archive || {};
|
||||
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO);
|
||||
|
||||
/**
|
||||
* The information message.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
* The information message.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.msg = msg;
|
||||
};
|
||||
bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent);
|
||||
@@ -111,10 +174,10 @@ bitjs.archive = bitjs.archive || {};
|
||||
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR);
|
||||
|
||||
/**
|
||||
* The information message.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
* The information message.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.msg = msg;
|
||||
};
|
||||
bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent);
|
||||
@@ -178,8 +241,8 @@ bitjs.archive = bitjs.archive || {};
|
||||
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT);
|
||||
|
||||
/**
|
||||
* @type {UnarchivedFile}
|
||||
*/
|
||||
* @type {UnarchivedFile}
|
||||
*/
|
||||
this.unarchivedFile = unarchivedFile;
|
||||
};
|
||||
bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent);
|
||||
@@ -194,23 +257,23 @@ bitjs.archive = bitjs.archive || {};
|
||||
*/
|
||||
bitjs.archive.Unarchiver = function(arrayBuffer, optPathToBitJS) {
|
||||
/**
|
||||
* The ArrayBuffer object.
|
||||
* @type {ArrayBuffer}
|
||||
* @protected
|
||||
*/
|
||||
* The ArrayBuffer object.
|
||||
* @type {ArrayBuffer}
|
||||
* @protected
|
||||
*/
|
||||
this.ab = arrayBuffer;
|
||||
|
||||
/**
|
||||
* The path to the BitJS files.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.pathToBitJS_ = optPathToBitJS || "";
|
||||
* The path to the BitJS files.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.pathToBitJS_ = optPathToBitJS || "/";
|
||||
|
||||
/**
|
||||
* A map from event type to an array of listeners.
|
||||
* @type {Map.<string, Array>}
|
||||
*/
|
||||
* A map from event type to an array of listeners.
|
||||
* @type {Map.<string, Array>}
|
||||
*/
|
||||
this.listeners_ = {};
|
||||
for (var type in bitjs.archive.UnarchiveEvent.Type) {
|
||||
this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = [];
|
||||
@@ -282,7 +345,7 @@ bitjs.archive = bitjs.archive || {};
|
||||
|
||||
/**
|
||||
* Starts the unarchive in a separate Web Worker thread and returns immediately.
|
||||
*/
|
||||
*/
|
||||
bitjs.archive.Unarchiver.prototype.start = function() {
|
||||
var me = this;
|
||||
var scriptFileName = this.pathToBitJS_ + this.getScriptFileName();
|
||||
@@ -354,4 +417,26 @@ bitjs.archive = bitjs.archive || {};
|
||||
return "untar.js";
|
||||
};
|
||||
|
||||
/**
|
||||
* Factory method that creates an unarchiver based on the byte signature found
|
||||
* in the arrayBuffer.
|
||||
* @param {ArrayBuffer} ab
|
||||
* @param {string=} optPathToBitJS Path to the unarchiver script files.
|
||||
* @return {bitjs.archive.Unarchiver}
|
||||
*/
|
||||
bitjs.archive.GetUnarchiver = function(ab, optPathToBitJS) {
|
||||
var unarchiver = null;
|
||||
var pathToBitJS = optPathToBitJS || "";
|
||||
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
|
||||
unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS);
|
||||
}
|
||||
return unarchiver;
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,864 @@
|
||||
/**
|
||||
* rarvm.js
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2017 Google Inc.
|
||||
*/
|
||||
|
||||
/**
|
||||
* CRC Implementation.
|
||||
*/
|
||||
/* global Uint8Array, Uint32Array, bitjs, DataView */
|
||||
/* exported MAXWINMASK, UnpackFilter */
|
||||
|
||||
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 VmCommands = {
|
||||
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 VmStandardFilters = {
|
||||
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 VmFlags = {
|
||||
VM_FC: 1,
|
||||
VM_FZ: 2,
|
||||
VM_FS: 0x80000000,
|
||||
};
|
||||
|
||||
/**
|
||||
*/
|
||||
var VmOpType = {
|
||||
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 === VmCommands) {
|
||||
s = "VmCommands.";
|
||||
} else if (obj === VmStandardFilters) {
|
||||
s = "VmStandardFilters.";
|
||||
} else if (obj === VmFlags) {
|
||||
s = "VmOpType.";
|
||||
} else if (obj === VmOpType) {
|
||||
s = "VmOpType.";
|
||||
}
|
||||
|
||||
return s + findKeyForValue(obj, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @struct
|
||||
* @constructor
|
||||
*/
|
||||
var VmPreparedOperand = function() {
|
||||
/** @type {VmOpType} */
|
||||
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} */
|
||||
VmPreparedOperand.prototype.toString = function() {
|
||||
if (this.Type === null) {
|
||||
return "Error: Type was null in VmPreparedOperand";
|
||||
}
|
||||
return "{ " +
|
||||
"Type: " + getDebugString(VmOpType, this.Type) +
|
||||
", Data: " + this.Data +
|
||||
", Base: " + this.Base +
|
||||
" }";
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct
|
||||
* @constructor
|
||||
*/
|
||||
var VmPreparedCommand = function() {
|
||||
/** @type {VmCommands} */
|
||||
this.OpCode;
|
||||
|
||||
/** @type {boolean} */
|
||||
this.ByteMode = false;
|
||||
|
||||
/** @type {VmPreparedOperand} */
|
||||
this.Op1 = new VmPreparedOperand();
|
||||
|
||||
/** @type {VmPreparedOperand} */
|
||||
this.Op2 = new VmPreparedOperand();
|
||||
};
|
||||
|
||||
/** @return {string} */
|
||||
VmPreparedCommand.prototype.toString = function(indent) {
|
||||
if (this.OpCode === null) {
|
||||
return "Error: OpCode was null in VmPreparedCommand";
|
||||
}
|
||||
indent = indent || "";
|
||||
return indent + "{\n" +
|
||||
indent + " OpCode: " + getDebugString(VmCommands, this.OpCode) + ",\n" +
|
||||
indent + " ByteMode: " + this.ByteMode + ",\n" +
|
||||
indent + " Op1: " + this.Op1.toString() + ",\n" +
|
||||
indent + " Op2: " + this.Op2.toString() + ",\n" +
|
||||
indent + "}";
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct
|
||||
* @constructor
|
||||
*/
|
||||
var VmPreparedProgram = function() {
|
||||
/** @type {Array<VmPreparedCommand>} */
|
||||
this.Cmd = [];
|
||||
|
||||
/** @type {Array<VmPreparedCommand>} */
|
||||
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} */
|
||||
VmPreparedProgram.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 {VmPreparedProgram} */
|
||||
this.Prg = new VmPreparedProgram();
|
||||
};
|
||||
|
||||
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 VmCmdFlags = [
|
||||
/* 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 {VmStandardFilters} type
|
||||
* @struct
|
||||
* @constructor
|
||||
*/
|
||||
var StandardFilterSignature = function(length, crc, type) {
|
||||
/** @type {number} */
|
||||
this.Length = length;
|
||||
|
||||
/** @type {number} */
|
||||
this.CRC = crc;
|
||||
|
||||
/** @type {VmStandardFilters} */
|
||||
this.Type = type;
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {Array<StandardFilterSignature>}
|
||||
*/
|
||||
var StdList = [
|
||||
new StandardFilterSignature(53, 0xad576887, VmStandardFilters.VMSF_E8),
|
||||
new StandardFilterSignature(57, 0x3cd7e57e, VmStandardFilters.VMSF_E8E9),
|
||||
new StandardFilterSignature(120, 0x3769893f, VmStandardFilters.VMSF_ITANIUM),
|
||||
new StandardFilterSignature(29, 0x0e06077d, VmStandardFilters.VMSF_DELTA),
|
||||
new StandardFilterSignature(149, 0x1c2c5dc8, VmStandardFilters.VMSF_RGB),
|
||||
new StandardFilterSignature(216, 0xbc85e701, VmStandardFilters.VMSF_AUDIO),
|
||||
new StandardFilterSignature(40, 0x46b9c560, VmStandardFilters.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 {VmStandardFilters}
|
||||
*/
|
||||
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 VmStandardFilters.VMSF_NONE;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {VmPreparedOperand} 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 = VmOpType.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 = VmOpType.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 = VmOpType.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 {VmPreparedProgram} 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.
|
||||
preparedCodes.OpCode = VmCommands.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<VmPreparedCommand>} 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 VmCommands.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 VmCommands.VM_STANDARD:
|
||||
this.executeStandardFilter(cmd.Op1.Data);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error("RarVM OpCode not supported: " + getDebugString(VmCommands, cmd.OpCode));
|
||||
break;
|
||||
} // switch (cmd.OpCode)
|
||||
codeIndex++;
|
||||
cmd = preparedCodes[codeIndex];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} filterType
|
||||
*/
|
||||
RarVM.prototype.executeStandardFilter = function(filterType) {
|
||||
switch (filterType) {
|
||||
case VmStandardFilters.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(VmStandardFilters, filterType));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} code
|
||||
* @param {VmPreparedProgram} prg
|
||||
*/
|
||||
RarVM.prototype.prepare = function(code, prg) {
|
||||
var codeSize = code.length;
|
||||
var i;
|
||||
var curCmd;
|
||||
|
||||
//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 (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 !== VmStandardFilters.VMSF_NONE) {
|
||||
// VM code is found among standard filters.
|
||||
curCmd = new VmPreparedCommand();
|
||||
prg.Cmd.push(curCmd);
|
||||
|
||||
curCmd.OpCode = VmCommands.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 = VmOpType.VM_OPNONE;
|
||||
curCmd.Op2.Type = VmOpType.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 (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) {
|
||||
curCmd = new VmPreparedCommand();
|
||||
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 (VmCmdFlags[curCmd.OpCode] & VMCF_BYTEMODE) {
|
||||
curCmd.ByteMode = (bstream.readBits(1) !== 0);
|
||||
} else {
|
||||
curCmd.ByteMode = 0;
|
||||
}
|
||||
curCmd.Op1.Type = VmOpType.VM_OPNONE;
|
||||
curCmd.Op2.Type = VmOpType.VM_OPNONE;
|
||||
var opNum = (VmCmdFlags[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 === VmOpType.VM_OPINT && (VmCmdFlags[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])
|
||||
|
||||
curCmd = new VmPreparedCommand();
|
||||
prg.Cmd.push(curCmd);
|
||||
curCmd.OpCode = VmCommands.VM_RET;
|
||||
// TODO: Addr=&CurCmd->Op1.Data
|
||||
curCmd.Op1.Addr = [curCmd.Op1.Data];
|
||||
curCmd.Op2.Addr = [curCmd.Op2.Data];
|
||||
curCmd.Op1.Type = VmOpType.VM_OPNONE;
|
||||
curCmd.Op2.Type = VmOpType.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 (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
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2011 Google Inc.
|
||||
* Copyright(c) 2011 antimatter15
|
||||
*
|
||||
@@ -8,11 +10,14 @@
|
||||
*
|
||||
* http://kthoom.googlecode.com/hg/docs/unrar.html
|
||||
*/
|
||||
/* global bitjs, importScripts */
|
||||
/* global bitjs, importScripts, RarVM, Uint8Array, UnpackFilter */
|
||||
/* global VM_FIXEDGLOBALSIZE, VM_GLOBALMEMSIZE, MAXWINMASK, VM_GLOBALMEMADDR, MAXWINSIZE */
|
||||
|
||||
// This file expects to be invoked as a Worker (see onmessage below).
|
||||
importScripts("io.js");
|
||||
importScripts("../io/bitstream.js");
|
||||
importScripts("../io/bytebuffer.js");
|
||||
importScripts("archive.js");
|
||||
importScripts("rarvm.js");
|
||||
|
||||
// Progress variables.
|
||||
var currentFilename = "";
|
||||
@@ -59,11 +64,15 @@ var MAIN_HEAD = 0x73,
|
||||
// PROTECT_HEAD = 0x78,
|
||||
// SIGN_HEAD = 0x79,
|
||||
// NEWSUB_HEAD = 0x7a,
|
||||
ENDARC_HEAD = 0x7b;
|
||||
ENDARC_HEAD = 0x7b;
|
||||
|
||||
// bstream is a bit stream
|
||||
// ============================================================================================== //
|
||||
|
||||
/**
|
||||
* @param {bitjs.io.BitStream} bstream
|
||||
* @constructor
|
||||
*/
|
||||
var RarVolumeHeader = function(bstream) {
|
||||
|
||||
var headPos = bstream.bytePtr;
|
||||
// byte 1,2
|
||||
info("Rar Volume Header @" + bstream.bytePtr);
|
||||
@@ -197,15 +206,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;
|
||||
default:
|
||||
info("Found a header of type 0x" + byteValueToHexString(this.headType));
|
||||
// skip the rest of the header bytes (for now)
|
||||
bstream.readBytes( this.headSize - 7 );
|
||||
bstream.readBytes(this.headSize - 7);
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -213,20 +225,22 @@ var RarVolumeHeader = function(bstream) {
|
||||
//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],
|
||||
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],
|
||||
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,
|
||||
48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072,
|
||||
4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304,
|
||||
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,
|
||||
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;
|
||||
|
||||
@@ -266,15 +280,51 @@ var RD = { //rep decode
|
||||
DecodeNum: new Array(rRC)
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {Array<bitjs.io.ByteBuffer>}
|
||||
*/
|
||||
var rOldBuffers = [];
|
||||
|
||||
/**
|
||||
* The current buffer we are unpacking to.
|
||||
* @type {bitjs.io.ByteBuffer}
|
||||
*/
|
||||
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;
|
||||
|
||||
var lowDistRepCount = 0;
|
||||
var prevLowDist = 0;
|
||||
|
||||
var rOldDist = [0, 0, 0, 0];
|
||||
var lastDist;
|
||||
var lastLength;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var BitLength = new Array(rBC),
|
||||
Table = new Array(rHuffTableSize);
|
||||
var BitLength = new Array(rBC);
|
||||
var Table = new Array(rHuffTableSize);
|
||||
var i;
|
||||
// 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)) {
|
||||
info("Error! PPM not implemented yet");
|
||||
@@ -282,12 +332,13 @@ function rarReadTables(bstream) {
|
||||
}
|
||||
|
||||
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
|
||||
for (var I = 0; I < rBC; ++I) {
|
||||
|
||||
var Length = bstream.readBits(4);
|
||||
if (Length === 15) {
|
||||
var ZeroCount = bstream.readBits(4);
|
||||
@@ -346,24 +397,26 @@ function rarReadTables(bstream) {
|
||||
|
||||
|
||||
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;
|
||||
//some sort of rolled out binary search
|
||||
var bits = ((bitField < DecodeLen[8]) ?
|
||||
((bitField < DecodeLen[4]) ?
|
||||
((bitField < DecodeLen[2]) ?
|
||||
((bitField < DecodeLen[1]) ? 1 : 2)
|
||||
: ((bitField < DecodeLen[3]) ? 3 : 4))
|
||||
: (bitField < DecodeLen[6]) ?
|
||||
((bitField < DecodeLen[5]) ? 5 : 6)
|
||||
: ((bitField < DecodeLen[7]) ? 7 : 8))
|
||||
: ((bitField < DecodeLen[12]) ?
|
||||
((bitField < DecodeLen[1]) ? 1 : 2) :
|
||||
((bitField < DecodeLen[3]) ? 3 : 4)) :
|
||||
(bitField < DecodeLen[6]) ?
|
||||
((bitField < DecodeLen[5]) ? 5 : 6) :
|
||||
((bitField < DecodeLen[7]) ? 7 : 8)) :
|
||||
((bitField < DecodeLen[12]) ?
|
||||
((bitField < DecodeLen[10]) ?
|
||||
((bitField < DecodeLen[9]) ? 9 : 10)
|
||||
: ((bitField < DecodeLen[11]) ? 11 : 12))
|
||||
: (bitField < DecodeLen[14]) ?
|
||||
((bitField < DecodeLen[13]) ? 13 : 14)
|
||||
: 15));
|
||||
((bitField < DecodeLen[9]) ? 9 : 10) :
|
||||
((bitField < DecodeLen[11]) ? 11 : 12)) :
|
||||
(bitField < DecodeLen[14]) ?
|
||||
((bitField < DecodeLen[13]) ? 13 : 14) :
|
||||
15));
|
||||
bstream.readBits(bits);
|
||||
var N = DecodePos[bits] + ((bitField - DecodeLen[bits - 1]) >>> (16 - bits));
|
||||
|
||||
@@ -372,12 +425,17 @@ function rarDecodeNumber(bstream, dec) {
|
||||
|
||||
|
||||
function rarMakeDecodeTables(BitLength, offset, dec, size) {
|
||||
var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum;
|
||||
var LenCount = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
TmpPos = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
N = 0, M = 0;
|
||||
var DecodeLen = dec.DecodeLen;
|
||||
var DecodePos = dec.DecodePos;
|
||||
var DecodeNum = dec.DecodeNum;
|
||||
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;
|
||||
for (i = DecodeNum.length; i--;) DecodeNum[i] = 0;
|
||||
for (i = DecodeNum.length; i--;) {
|
||||
DecodeNum[i] = 0;
|
||||
}
|
||||
for (i = 0; i < size; i++) {
|
||||
LenCount[BitLength[i + offset] & 0xF]++;
|
||||
}
|
||||
@@ -399,24 +457,27 @@ function rarMakeDecodeTables(BitLength, offset, dec, size) {
|
||||
}
|
||||
for (I = 0; I < size; ++I) {
|
||||
if (BitLength[I + offset] !== 0) {
|
||||
DecodeNum[ TmpPos[ BitLength[offset + I] & 0xF ]++] = I;
|
||||
DecodeNum[TmpPos[BitLength[offset + I] & 0xF]++] = I;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: implement
|
||||
function Unpack15() { //bstream, Solid) {
|
||||
/**
|
||||
* @param {bitjs.io.BitStream} bstream
|
||||
* @param {boolean} Solid
|
||||
*/
|
||||
function unpack15() { //bstream, Solid) {
|
||||
info("ERROR! RAR 1.5 compression not supported");
|
||||
}
|
||||
|
||||
var lowDistRepCount = 0, prevLowDist = 0;
|
||||
|
||||
var rOldDist = [0, 0, 0, 0];
|
||||
|
||||
var lastDist = 0;
|
||||
var lastLength = 0;
|
||||
|
||||
function Unpack20(bstream) { //, Solid) {
|
||||
/**
|
||||
* Unpacks the bit stream into rBuffer using the Unpack20 algorithm.
|
||||
* @param {bitjs.io.BitStream} bstream
|
||||
* @param {boolean} Solid
|
||||
*/
|
||||
function unpack20(bstream) { //, Solid) {
|
||||
var destUnpSize = rBuffer.data.length;
|
||||
var oldDistPtr = 0;
|
||||
var Length;
|
||||
@@ -441,7 +502,9 @@ function Unpack20(bstream) { //, Solid) {
|
||||
}
|
||||
if (Distance >= 0x2000) {
|
||||
Length++;
|
||||
if (Distance >= 0x40000) Length++;
|
||||
if (Distance >= 0x40000) {
|
||||
Length++;
|
||||
}
|
||||
}
|
||||
lastLength = Length;
|
||||
lastDist = rOldDist[oldDistPtr++ & 3] = Distance;
|
||||
@@ -450,9 +513,7 @@ function Unpack20(bstream) { //, Solid) {
|
||||
}
|
||||
if (num === 269) {
|
||||
rarReadTables20(bstream);
|
||||
|
||||
rarUpdateProgress();
|
||||
|
||||
continue;
|
||||
}
|
||||
if (num === 256) {
|
||||
@@ -471,7 +532,9 @@ function Unpack20(bstream) { //, Solid) {
|
||||
Length++;
|
||||
if (Distance >= 0x2000) {
|
||||
Length++;
|
||||
if (Distance >= 0x40000) Length++;
|
||||
if (Distance >= 0x40000) {
|
||||
Length++;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastLength = Length;
|
||||
@@ -500,7 +563,6 @@ function rarUpdateProgress() {
|
||||
postProgress();
|
||||
}
|
||||
|
||||
|
||||
var rNC20 = 298,
|
||||
rDC20 = 48,
|
||||
rRC20 = 28,
|
||||
@@ -516,7 +578,9 @@ function rarReadTables20(bstream) {
|
||||
var i;
|
||||
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;
|
||||
for (I = 0; I < rBC20; I++) {
|
||||
@@ -549,11 +613,248 @@ function rarReadTables20(bstream) {
|
||||
rarMakeDecodeTables(Table, 0, LD, rNC20);
|
||||
rarMakeDecodeTables(Table, rNC20, DD, rDC20);
|
||||
rarMakeDecodeTables(Table, rNC20 + rDC20, RD, rRC20);
|
||||
for (i = UnpOldTable20.length; i--;) UnpOldTable20[i] = Table[i];
|
||||
for (i = UnpOldTable20.length; i--;) {
|
||||
UnpOldTable20[i] = Table[i];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================================== //
|
||||
|
||||
// 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 = [];
|
||||
}
|
||||
|
||||
|
||||
function Unpack29(bstream) {
|
||||
/**
|
||||
* @param {number} firstByte The first byte (flags).
|
||||
* @param {Uint8Array} vmCode An array of bytes.
|
||||
*/
|
||||
function rarAddVMCode(firstByte, vmCode) {
|
||||
VM.init();
|
||||
var i;
|
||||
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 (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 (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 (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;
|
||||
}
|
||||
vmCode = new Uint8Array(vmCodeSize);
|
||||
for (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 (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 (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 (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 (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) {
|
||||
// lazy initialize rDDecode and rDBits
|
||||
|
||||
var DDecode = new Array(rDC);
|
||||
@@ -577,7 +878,9 @@ function Unpack29(bstream) {
|
||||
lastDist = 0;
|
||||
lastLength = 0;
|
||||
var i;
|
||||
for (i = UnpOldTable.length; i--;) UnpOldTable[i] = 0;
|
||||
for (i = UnpOldTable.length; i--;) {
|
||||
UnpOldTable[i] = 0;
|
||||
}
|
||||
|
||||
// read in Huffman tables
|
||||
rarReadTables(bstream);
|
||||
@@ -632,16 +935,19 @@ function Unpack29(bstream) {
|
||||
continue;
|
||||
}
|
||||
if (num === 256) {
|
||||
if (!rarReadEndOfBlock(bstream)) break;
|
||||
if (!rarReadEndOfBlock(bstream)) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (num === 257) {
|
||||
//console.log("READVMCODE");
|
||||
if (!rarReadVMCode(bstream)) break;
|
||||
if (!rarReadVMCode(bstream)) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (num === 258) {
|
||||
if (lastLength != 0) {
|
||||
if (lastLength !== 0) {
|
||||
rarCopyString(lastLength, lastDist);
|
||||
}
|
||||
continue;
|
||||
@@ -650,8 +956,8 @@ function Unpack29(bstream) {
|
||||
var DistNum = num - 259;
|
||||
Distance = rOldDist[DistNum];
|
||||
|
||||
for (var I = DistNum; I > 0; I--) {
|
||||
rOldDist[I] = rOldDist[I - 1];
|
||||
for (var I2 = DistNum; I2 > 0; I2--) {
|
||||
rOldDist[I2] = rOldDist[I2 - 1];
|
||||
}
|
||||
rOldDist[0] = Distance;
|
||||
|
||||
@@ -674,15 +980,195 @@ function Unpack29(bstream) {
|
||||
rarCopyString(2, Distance);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} // while (true)
|
||||
rarUpdateProgress();
|
||||
rarWriteBuf();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
var j;
|
||||
var flt;
|
||||
for (var i = 0; i < PrgStack.length; ++i) {
|
||||
flt = PrgStack[i];
|
||||
if (flt === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flt.NextWindow) {
|
||||
flt.NextWindow = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var blockStart = flt.BlockStart;
|
||||
var blockLength = flt.BlockLength;
|
||||
var parentPrg;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
var globalDataLen;
|
||||
|
||||
if (prg.GlobalData.length > VM_FIXEDGLOBALSIZE) {
|
||||
// Save global data for next script execution.
|
||||
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);
|
||||
|
||||
parentPrg = Filters[nextFilter.ParentFilter].Prg;
|
||||
var nextPrg = nextFilter.Prg;
|
||||
|
||||
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.
|
||||
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 (j = 0; j < filteredData.length; ++j) {
|
||||
wBuffer.insertByte(filteredData[j]);
|
||||
}
|
||||
writeSize = (rBuffer.ptr - wBuffer.ptr) & MAXWINMASK;
|
||||
} else { // if (blockLength <= writeSize)
|
||||
for (j = i; j < PrgStack.length; ++j) {
|
||||
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();
|
||||
|
||||
var NewTable = false, NewFile = false;
|
||||
var NewTable = false,
|
||||
NewFile = false;
|
||||
if (bstream.readBits(1)) {
|
||||
NewTable = true;
|
||||
} else {
|
||||
@@ -690,32 +1176,7 @@ function rarReadEndOfBlock(bstream) {
|
||||
NewTable = !!bstream.readBits(1);
|
||||
}
|
||||
//tablesRead = !NewTable;
|
||||
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(FirstByte, vmCode, Length);
|
||||
}
|
||||
|
||||
function RarAddVMCode(firstByte, vmCode, length) {
|
||||
//console.log(vmCode);
|
||||
if (vmCode.length > 0) {
|
||||
info("Error! RarVM not supported yet!");
|
||||
}
|
||||
return true;
|
||||
return !(NewFile || (NewTable && !rarReadTables(bstream)));
|
||||
}
|
||||
|
||||
function rarInsertLastMatch(length, distance) {
|
||||
@@ -728,63 +1189,73 @@ function rarInsertOldDist(distance) {
|
||||
rOldDist.splice(0, 0, distance);
|
||||
}
|
||||
|
||||
//this is the real function, the other one is for debugging
|
||||
/**
|
||||
* Copies len bytes from distance bytes ago in the buffer to the end of the
|
||||
* 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) {
|
||||
var destPtr = rBuffer.ptr - distance;
|
||||
if (destPtr < 0) {
|
||||
var srcPtr = rBuffer.ptr - distance;
|
||||
if (srcPtr < 0) {
|
||||
var l = rOldBuffers.length;
|
||||
while (destPtr < 0) {
|
||||
destPtr = rOldBuffers[--l].data.length + destPtr;
|
||||
while (srcPtr < 0) {
|
||||
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) {
|
||||
while (length--) rBuffer.insertByte(rBuffer.data[destPtr++]);
|
||||
while (length--) {
|
||||
rBuffer.insertByte(rBuffer.data[srcPtr++]);
|
||||
}
|
||||
} else {
|
||||
rBuffer.insertBytes(rBuffer.data.subarray(destPtr, destPtr + length));
|
||||
rBuffer.insertBytes(rBuffer.data.subarray(srcPtr, srcPtr + length));
|
||||
}
|
||||
}
|
||||
|
||||
var rOldBuffers = [];
|
||||
// v must be a valid RarVolume
|
||||
/**
|
||||
* @param {RarLocalFile} v
|
||||
*/
|
||||
function unpack(v) {
|
||||
|
||||
// TODO: implement what happens when unpVer is < 15
|
||||
var Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer,
|
||||
Solid = v.header.LHD_SOLID,
|
||||
bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength );
|
||||
var Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer;
|
||||
// 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);
|
||||
|
||||
info("Unpacking " + v.filename + " RAR v" + Ver);
|
||||
|
||||
switch(Ver) {
|
||||
switch (Ver) {
|
||||
case 15: // rar 1.5 compression
|
||||
Unpack15(); //(bstream, Solid);
|
||||
unpack15(); //(bstream, Solid);
|
||||
break;
|
||||
case 20: // rar 2.x compression
|
||||
case 26: // files larger than 2GB
|
||||
Unpack20(bstream); //, Solid);
|
||||
unpack20(bstream); //, Solid);
|
||||
break;
|
||||
case 29: // rar 3.x compression
|
||||
case 36: // alternative hash
|
||||
Unpack29(bstream);
|
||||
wBuffer = new bitjs.io.ByteBuffer(rBuffer.data.length);
|
||||
unpack29(bstream);
|
||||
break;
|
||||
} // switch(method)
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// bstream is a bit stream
|
||||
var RarLocalFile = function(bstream) {
|
||||
|
||||
this.header = new RarVolumeHeader(bstream);
|
||||
this.filename = this.header.filename;
|
||||
|
||||
if (this.header.headType != FILE_HEAD && this.header.headType != ENDARC_HEAD) {
|
||||
if (this.header.headType !== FILE_HEAD && this.header.headType !== ENDARC_HEAD) {
|
||||
this.isValid = false;
|
||||
info("Error! RAR Volume did not include a FILE_HEAD header ");
|
||||
} else {
|
||||
@@ -798,7 +1269,6 @@ var RarLocalFile = function(bstream) {
|
||||
};
|
||||
|
||||
RarLocalFile.prototype.unrar = function() {
|
||||
|
||||
if (!this.header.flags.LHD_SPLIT_BEFORE) {
|
||||
// unstore file
|
||||
if (this.header.method === 0x30) {
|
||||
@@ -836,11 +1306,10 @@ var unrar = function(arrayBuffer) {
|
||||
header.headType === 0x72 &&
|
||||
header.flags.value === 0x1A21 &&
|
||||
header.headSize === 7) {
|
||||
|
||||
info("Found RAR signature");
|
||||
|
||||
var mhead = new RarVolumeHeader(bstream);
|
||||
if (mhead.headType != MAIN_HEAD) {
|
||||
if (mhead.headType !== MAIN_HEAD) {
|
||||
info("Error! RAR did not include a MAIN_HEAD header");
|
||||
} else {
|
||||
var localFiles = [];
|
||||
@@ -859,18 +1328,15 @@ var unrar = function(arrayBuffer) {
|
||||
break;
|
||||
}
|
||||
//info("bstream" + bstream.bytePtr+"/"+bstream.bytes.length);
|
||||
} while ( localFile.isValid );
|
||||
} while (localFile.isValid);
|
||||
totalFilesInArchive = localFiles.length;
|
||||
|
||||
// now we have all information but things are unpacked
|
||||
// TODO: unpack
|
||||
localFiles = localFiles.sort(function(a, b) {
|
||||
var aname = a.filename.toLowerCase();
|
||||
var bname = b.filename.toLowerCase();
|
||||
return aname > bname ? 1 : -1;
|
||||
});
|
||||
localFiles.sort(alphanumCase);
|
||||
|
||||
info(localFiles.map(function(a) {return a.filename;}).join(", "));
|
||||
info(localFiles.map(function(a) {
|
||||
return a.filename;
|
||||
}).join(", "));
|
||||
for (var i = 0; i < localFiles.length; ++i) {
|
||||
var localfile = localFiles[i];
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/* global bitjs, importScripts, Uint8Array */
|
||||
|
||||
// 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;
|
||||
var allLocalFiles = [];
|
||||
|
||||
// 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();
|
||||
/*
|
||||
// go through whole file, read header of each block and memorize, filepointer
|
||||
*/
|
||||
while (bstream.peekNumber(4) !== 0) {
|
||||
var localFile = new TarLocalFile(bstream);
|
||||
allLocalFiles.push(localFile);
|
||||
postProgress();
|
||||
}
|
||||
// got all local files, now sort them
|
||||
allLocalFiles.sort(alphanumCase);
|
||||
|
||||
allLocalFiles.forEach(function(oneLocalFile) {
|
||||
// While we don't encounter an empty block, keep making TarLocalFiles.
|
||||
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.
|
||||
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 {
|
||||
err("Found an error while untarring");
|
||||
err(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
/**
|
||||
* unzip.js
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2011 Google Inc.
|
||||
* Copyright(c) 2011 antimatter15
|
||||
*
|
||||
@@ -12,7 +14,9 @@
|
||||
/* global bitjs, importScripts, Uint8Array*/
|
||||
|
||||
// This file expects to be invoked as a Worker (see onmessage below).
|
||||
importScripts("io.js");
|
||||
importScripts("../io/bitstream.js");
|
||||
importScripts("../io/bytebuffer.js");
|
||||
importScripts("../io/bytestream.js");
|
||||
importScripts("archive.js");
|
||||
|
||||
// Progress variables.
|
||||
@@ -47,7 +51,7 @@ var zDigitalSignatureSignature = 0x05054b50;
|
||||
|
||||
// takes a ByteStream and parses out the local file information
|
||||
var ZipLocalFile = function(bstream) {
|
||||
if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function() {}) {
|
||||
if (typeof bstream !== typeof {} || !bstream.readNumber || typeof bstream.readNumber !== typeof function() {}) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -68,23 +72,10 @@ var ZipLocalFile = function(bstream) {
|
||||
this.filename = bstream.readString(this.fileNameLength);
|
||||
}
|
||||
|
||||
info("Zip Local File Header:");
|
||||
info(" version=" + this.version);
|
||||
info(" general purpose=" + this.generalPurpose);
|
||||
info(" compression method=" + this.compressionMethod);
|
||||
info(" last mod file time=" + this.lastModFileTime);
|
||||
info(" last mod file date=" + this.lastModFileDate);
|
||||
info(" crc32=" + this.crc32);
|
||||
info(" compressed size=" + this.compressedSize);
|
||||
info(" uncompressed size=" + this.uncompressedSize);
|
||||
info(" file name length=" + this.fileNameLength);
|
||||
info(" extra field length=" + this.extraFieldLength);
|
||||
info(" filename = '" + this.filename + "'");
|
||||
|
||||
this.extraField = null;
|
||||
if (this.extraFieldLength > 0) {
|
||||
this.extraField = bstream.readString(this.extraFieldLength);
|
||||
info(" extra field=" + this.extraField);
|
||||
this.extraField = bstream.readString(this.extraFieldLength);
|
||||
info(" extra field=" + this.extraField);
|
||||
}
|
||||
|
||||
// read in the compressed data
|
||||
@@ -98,29 +89,42 @@ var ZipLocalFile = function(bstream) {
|
||||
// "This descriptor exists only if bit 3 of the general purpose bit flag is set"
|
||||
// But how do you figure out how big the file data is if you don't know the compressedSize
|
||||
// from the header?!?
|
||||
if ((this.generalPurpose & bitjs.BIT[3]) != 0) {
|
||||
if ((this.generalPurpose & bitjs.BIT[3]) !== 0) {
|
||||
this.crc32 = bstream.readNumber(4);
|
||||
this.compressedSize = bstream.readNumber(4);
|
||||
this.uncompressedSize = bstream.readNumber(4);
|
||||
}
|
||||
|
||||
// Now that we have all the bytes for this file, we can print out some information.
|
||||
info("Zip Local File Header:");
|
||||
info(" version=" + this.version);
|
||||
info(" general purpose=" + this.generalPurpose);
|
||||
info(" compression method=" + this.compressionMethod);
|
||||
info(" last mod file time=" + this.lastModFileTime);
|
||||
info(" last mod file date=" + this.lastModFileDate);
|
||||
info(" crc32=" + this.crc32);
|
||||
info(" compressed size=" + this.compressedSize);
|
||||
info(" uncompressed size=" + this.uncompressedSize);
|
||||
info(" file name length=" + this.fileNameLength);
|
||||
info(" extra field length=" + this.extraFieldLength);
|
||||
info(" filename = '" + this.filename + "'");
|
||||
|
||||
};
|
||||
|
||||
// determine what kind of compressed data we have and decompress
|
||||
ZipLocalFile.prototype.unzip = function() {
|
||||
|
||||
// Zip Version 1.0, no compression (store only)
|
||||
if (this.compressionMethod == 0 ) {
|
||||
if (this.compressionMethod === 0 ) {
|
||||
info("ZIP v" + this.version + ", store only: " + this.filename + " (" + this.compressedSize + " bytes)");
|
||||
currentBytesUnarchivedInFile = this.compressedSize;
|
||||
currentBytesUnarchived += this.compressedSize;
|
||||
this.fileData = zeroCompression(this.fileData, this.uncompressedSize);
|
||||
}
|
||||
// version == 20, compression method == 8 (DEFLATE)
|
||||
else if (this.compressionMethod == 8) {
|
||||
} else if (this.compressionMethod === 8) {
|
||||
// version == 20, compression method == 8 (DEFLATE)
|
||||
info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)");
|
||||
this.fileData = inflate(this.fileData, this.uncompressedSize);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)");
|
||||
this.fileData = null;
|
||||
}
|
||||
@@ -130,6 +134,7 @@ ZipLocalFile.prototype.unzip = function() {
|
||||
// Takes an ArrayBuffer of a zip file in
|
||||
// returns null on error
|
||||
// returns an array of DecompressedFile objects on success
|
||||
// ToDo This function differs
|
||||
var unzip = function(arrayBuffer) {
|
||||
postMessage(new bitjs.archive.UnarchiveStartEvent());
|
||||
|
||||
@@ -143,10 +148,10 @@ var unzip = function(arrayBuffer) {
|
||||
|
||||
var bstream = new bitjs.io.ByteStream(arrayBuffer);
|
||||
// detect local file header signature or return null
|
||||
if (bstream.peekNumber(4) == zLocalFileHeaderSignature) {
|
||||
if (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
|
||||
var localFiles = [];
|
||||
// loop until we don't see any more local files
|
||||
while (bstream.peekNumber(4) == zLocalFileHeaderSignature) {
|
||||
while (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
|
||||
var oneLocalFile = new ZipLocalFile(bstream);
|
||||
// this should strip out directories/folders
|
||||
if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) {
|
||||
@@ -157,14 +162,10 @@ var unzip = function(arrayBuffer) {
|
||||
totalFilesInArchive = localFiles.length;
|
||||
|
||||
// got all local files, now sort them
|
||||
localFiles.sort(function(a, b) {
|
||||
var aname = a.filename.toLowerCase();
|
||||
var bname = b.filename.toLowerCase();
|
||||
return aname > bname ? 1 : -1;
|
||||
});
|
||||
localFiles.sort(alphanumCase);
|
||||
|
||||
// archive extra data record
|
||||
if (bstream.peekNumber(4) == zArchiveExtraDataSignature) {
|
||||
if (bstream.peekNumber(4) === zArchiveExtraDataSignature) {
|
||||
info(" Found an Archive Extra Data Signature");
|
||||
|
||||
// skipping this record for now
|
||||
@@ -175,11 +176,11 @@ var unzip = function(arrayBuffer) {
|
||||
|
||||
// central directory structure
|
||||
// TODO: handle the rest of the structures (Zip64 stuff)
|
||||
if (bstream.peekNumber(4) == zCentralFileHeaderSignature) {
|
||||
if (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
|
||||
info(" Found a Central File Header");
|
||||
|
||||
// read all file headers
|
||||
while (bstream.peekNumber(4) == zCentralFileHeaderSignature) {
|
||||
while (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
|
||||
bstream.readNumber(4); // signature
|
||||
bstream.readNumber(2); // version made by
|
||||
bstream.readNumber(2); // version needed to extract
|
||||
@@ -205,7 +206,7 @@ var unzip = function(arrayBuffer) {
|
||||
}
|
||||
|
||||
// digital signature
|
||||
if (bstream.peekNumber(4) == zDigitalSignatureSignature) {
|
||||
if (bstream.peekNumber(4) === zDigitalSignatureSignature) {
|
||||
info(" Found a Digital Signature");
|
||||
|
||||
bstream.readNumber(4);
|
||||
@@ -230,7 +231,7 @@ var unzip = function(arrayBuffer) {
|
||||
// actually do the unzipping
|
||||
localfile.unzip();
|
||||
|
||||
if (localfile.fileData != null) {
|
||||
if (localfile.fileData !== null) {
|
||||
postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile));
|
||||
postProgress();
|
||||
}
|
||||
@@ -245,26 +246,26 @@ var unzip = function(arrayBuffer) {
|
||||
// containing {length: 6, symbol: X}
|
||||
function getHuffmanCodes(bitLengths) {
|
||||
// ensure bitLengths is an array containing at least one element
|
||||
if (typeof bitLengths != typeof [] || bitLengths.length < 1) {
|
||||
if (typeof bitLengths !== typeof [] || bitLengths.length < 1) {
|
||||
err("Error! getHuffmanCodes() called with an invalid array");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reference: http://tools.ietf.org/html/rfc1951#page-8
|
||||
var numLengths = bitLengths.length,
|
||||
blCount = [],
|
||||
MAX_BITS = 1;
|
||||
var numLengths = bitLengths.length;
|
||||
var blCount = [];
|
||||
var MAX_BITS = 1;
|
||||
|
||||
// Step 1: count up how many codes of each length we have
|
||||
for (var i = 0; i < numLengths; ++i) {
|
||||
var length = bitLengths[i];
|
||||
// test to ensure each bit length is a positive, non-zero number
|
||||
if (typeof length != typeof 1 || length < 0) {
|
||||
if (typeof length !== typeof 1 || length < 0) {
|
||||
err("bitLengths contained an invalid number in getHuffmanCodes(): " + length + " of type " + (typeof length));
|
||||
return null;
|
||||
}
|
||||
// increment the appropriate bitlength count
|
||||
if (blCount[length] == undefined) blCount[length] = 0;
|
||||
if (typeof blCount[length] === "undefined") blCount[length] = 0;
|
||||
// a length of zero means this symbol is not participating in the huffman coding
|
||||
if (length > 0) blCount[length]++;
|
||||
|
||||
@@ -272,21 +273,22 @@ function getHuffmanCodes(bitLengths) {
|
||||
}
|
||||
|
||||
// Step 2: Find the numerical value of the smallest code for each code length
|
||||
var nextCode = [],
|
||||
code = 0;
|
||||
var nextCode = [];
|
||||
var code = 0;
|
||||
for (var bits = 1; bits <= MAX_BITS; ++bits) {
|
||||
var length = bits - 1;
|
||||
var length2 = bits - 1;
|
||||
// ensure undefined lengths are zero
|
||||
if (blCount[length] == undefined) blCount[length] = 0;
|
||||
if (typeof blCount[length2] === "undefined") blCount[length2] = 0;
|
||||
code = (code + blCount[bits - 1]) << 1;
|
||||
nextCode [bits] = code;
|
||||
}
|
||||
|
||||
// Step 3: Assign numerical values to all codes
|
||||
var table = {}, tableLength = 0;
|
||||
var table = {};
|
||||
var tableLength = 0;
|
||||
for (var n = 0; n < numLengths; ++n) {
|
||||
var len = bitLengths[n];
|
||||
if (len != 0) {
|
||||
if (len !== 0) {
|
||||
table[nextCode [len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(nextCode [len],len) };
|
||||
tableLength++;
|
||||
nextCode [len]++;
|
||||
@@ -316,6 +318,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
|
||||
var fixedHCtoLiteral = null;
|
||||
var fixedHCtoDistance = null;
|
||||
|
||||
function getFixedLiteralTable() {
|
||||
// create once
|
||||
if (!fixedHCtoLiteral) {
|
||||
@@ -331,6 +334,7 @@ function getFixedLiteralTable() {
|
||||
}
|
||||
return fixedHCtoLiteral;
|
||||
}
|
||||
|
||||
function getFixedDistanceTable() {
|
||||
// create once
|
||||
if (!fixedHCtoDistance) {
|
||||
@@ -348,7 +352,8 @@ function getFixedDistanceTable() {
|
||||
// extract one bit at a time until we find a matching Huffman Code
|
||||
// then return that symbol
|
||||
function decodeSymbol(bstream, hcTable) {
|
||||
var code = 0, len = 0;
|
||||
var code = 0;
|
||||
var len = 0;
|
||||
|
||||
// loop until we match
|
||||
for (;;) {
|
||||
@@ -358,8 +363,7 @@ function decodeSymbol(bstream, hcTable) {
|
||||
++len;
|
||||
|
||||
// check against Huffman Code table and break if found
|
||||
if (hcTable.hasOwnProperty(code) && hcTable[code].length == len) {
|
||||
|
||||
if (hcTable.hasOwnProperty(code) && hcTable[code].length === len) {
|
||||
break;
|
||||
}
|
||||
if (len > hcTable.maxLength) {
|
||||
@@ -387,16 +391,36 @@ Code Bits Length(s) Code Bits Lengths Code Bits Length(s)
|
||||
264 0 10 274 3 43-50 284 5 227-257
|
||||
265 1 11,12 275 3 51-58 285 0 258
|
||||
266 1 13,14 276 3 59-66
|
||||
|
||||
*/
|
||||
var LengthLookupTable = [
|
||||
[0, 3], [0, 4], [0, 5], [0, 6],
|
||||
[0, 7], [0, 8], [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, 3],
|
||||
[0, 4],
|
||||
[0, 5],
|
||||
[0, 6],
|
||||
[0, 7],
|
||||
[0, 8],
|
||||
[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]
|
||||
];
|
||||
/*
|
||||
@@ -415,20 +439,36 @@ var LengthLookupTable = [
|
||||
9 3 25-32 19 8 769-1024 29 13 24577-32768
|
||||
*/
|
||||
var DistLookupTable = [
|
||||
[0, 1], [0, 2], [0, 3], [0, 4],
|
||||
[1, 5], [1, 7],
|
||||
[2, 9], [2, 13],
|
||||
[3, 17], [3, 25],
|
||||
[4, 33], [4, 49],
|
||||
[5, 65], [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]
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
[0, 3],
|
||||
[0, 4],
|
||||
[1, 5],
|
||||
[1, 7],
|
||||
[2, 9],
|
||||
[2, 13],
|
||||
[3, 17],
|
||||
[3, 25],
|
||||
[4, 33],
|
||||
[4, 49],
|
||||
[5, 65],
|
||||
[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) {
|
||||
@@ -454,17 +494,15 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
|
||||
// copy literal byte to output
|
||||
buffer.insertByte(symbol);
|
||||
blockSize++;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// end of block reached
|
||||
if (symbol == 256) {
|
||||
if (symbol === 256) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
var lengthLookup = LengthLookupTable[symbol - 257],
|
||||
length = lengthLookup[1] + bstream.readBits(lengthLookup[0]),
|
||||
distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)],
|
||||
distance = distLookup[1] + bstream.readBits(distLookup[0]);
|
||||
} else {
|
||||
var lengthLookup = LengthLookupTable[symbol - 257];
|
||||
var length = lengthLookup[1] + bstream.readBits(lengthLookup[0]);
|
||||
var distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)];
|
||||
var distance = distLookup[1] + bstream.readBits(distLookup[0]);
|
||||
|
||||
// now apply length and distance appropriately and copy to output
|
||||
|
||||
@@ -485,9 +523,8 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
|
||||
buffer.insertByte(data[ch++]);
|
||||
}
|
||||
} else {
|
||||
buffer.insertBytes(buffer.data.subarray(ch, ch + length));
|
||||
buffer.insertBytes(buffer.data.subarray(ch, ch + length));
|
||||
}
|
||||
|
||||
} // length-distance pair
|
||||
} // length-distance pair or end-of-block
|
||||
} // loop until we reach end of block
|
||||
@@ -514,37 +551,36 @@ function inflate(compressedData, numDecompressedBytes) {
|
||||
compressedData.byteOffset,
|
||||
compressedData.byteLength);
|
||||
var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes);
|
||||
var numBlocks = 0, blockSize = 0;
|
||||
var blockSize = 0;
|
||||
|
||||
// block format: http://tools.ietf.org/html/rfc1951#page-9
|
||||
var bFinal = 0;
|
||||
do {
|
||||
var bFinal = bstream.readBits(1),
|
||||
bType = bstream.readBits(2);
|
||||
bFinal = bstream.readBits(1);
|
||||
var bType = bstream.readBits(2);
|
||||
blockSize = 0;
|
||||
++numBlocks;
|
||||
// ++numBlocks;
|
||||
// no compression
|
||||
if (bType == 0) {
|
||||
if (bType === 0) {
|
||||
// skip remaining bits in this byte
|
||||
while (bstream.bitPtr != 0) bstream.readBits(1);
|
||||
while (bstream.bitPtr !== 0) bstream.readBits(1);
|
||||
var len = bstream.readBits(16);
|
||||
bstream.readBits(16);
|
||||
// TODO: check if nlen is the ones-complement of len?
|
||||
// TODO: check if nlen is the ones-complement of len?
|
||||
|
||||
if (len > 0) buffer.insertBytes(bstream.readBytes(len));
|
||||
blockSize = len;
|
||||
}
|
||||
// fixed Huffman codes
|
||||
else if(bType == 1) {
|
||||
} else if (bType === 1) {
|
||||
// fixed Huffman codes
|
||||
blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer);
|
||||
}
|
||||
// dynamic Huffman codes
|
||||
else if(bType == 2) {
|
||||
} else if (bType === 2) {
|
||||
// dynamic Huffman codes
|
||||
var numLiteralLengthCodes = bstream.readBits(5) + 257;
|
||||
var numDistanceCodes = bstream.readBits(5) + 1,
|
||||
numCodeLengthCodes = bstream.readBits(4) + 4;
|
||||
|
||||
// populate the array of code length codes (first de-compaction)
|
||||
var codeLengthsCodeLengths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
var codeLengthsCodeLengths = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
for (var i = 0; i < numCodeLengthCodes; ++i) {
|
||||
codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3);
|
||||
}
|
||||
@@ -575,19 +611,17 @@ function inflate(compressedData, numDecompressedBytes) {
|
||||
if (symbol <= 15) {
|
||||
literalCodeLengths.push(symbol);
|
||||
prevCodeLength = symbol;
|
||||
}
|
||||
else if (symbol == 16) {
|
||||
} else if (symbol === 16) {
|
||||
var repeat = bstream.readBits(2) + 3;
|
||||
while (repeat--) {
|
||||
literalCodeLengths.push(prevCodeLength);
|
||||
}
|
||||
}
|
||||
else if (symbol == 17) {
|
||||
} else if (symbol === 17) {
|
||||
var repeat1 = bstream.readBits(3) + 3;
|
||||
while (repeat1--) {
|
||||
literalCodeLengths.push(0);
|
||||
}
|
||||
} else if (symbol == 18) {
|
||||
} else if (symbol === 18) {
|
||||
var repeat2 = bstream.readBits(7) + 11;
|
||||
while (repeat2--) {
|
||||
literalCodeLengths.push(0);
|
||||
@@ -599,8 +633,8 @@ function inflate(compressedData, numDecompressedBytes) {
|
||||
var distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes);
|
||||
|
||||
// now generate the true Huffman Code tables using these code lengths
|
||||
var hcLiteralTable = getHuffmanCodes(literalCodeLengths),
|
||||
hcDistanceTable = getHuffmanCodes(distanceCodeLengths);
|
||||
var hcLiteralTable = getHuffmanCodes(literalCodeLengths);
|
||||
var hcDistanceTable = getHuffmanCodes(distanceCodeLengths);
|
||||
blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer);
|
||||
} else {
|
||||
// error
|
||||
@@ -613,7 +647,7 @@ function inflate(compressedData, numDecompressedBytes) {
|
||||
currentBytesUnarchived += blockSize;
|
||||
postProgress();
|
||||
|
||||
} while (bFinal != 1);
|
||||
} while (bFinal !== 1);
|
||||
// we are done reading blocks if the bFinal bit was set for this block
|
||||
|
||||
// return the buffer data bytes
|
||||
@@ -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 ) {
|
||||
|
||||
description = $( '.comments' );
|
||||
bookInfo = $( '.author' ).nextUntil( 'h3:contains("Description")');
|
||||
$( 'h3:contains("Description")' ).detach();
|
||||
bookInfo = $( ".author" ).nextUntil("#decription");
|
||||
$("#decription").detach();
|
||||
$( '.comments' ).detach();
|
||||
$( bookInfo ).wrapAll( '<div class="bookinfo"></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>' );
|
||||
|
||||
// Fix-up book detail headings
|
||||
publisher = $( '.publishers p span' ).text().split( ':' );
|
||||
$( '.publishers p span' ).remove();
|
||||
$.each(publisher, function(i, val) {
|
||||
$( '.publishers' ).append( '<span>' + publisher[i] + '</span>' );
|
||||
publisher = $( '.publishers p span' ).text().split( ':' );
|
||||
$( '.publishers p span' ).remove();
|
||||
$.each(publisher, function(i, val) {
|
||||
$( '.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' )
|
||||
.text().split(': ');
|
||||
|
||||
+10
-10
@@ -20,16 +20,16 @@
|
||||
* Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only)
|
||||
*/
|
||||
/* global _, i18nMsg, tinymce */
|
||||
var dbResults = [];
|
||||
// var dbResults = [];
|
||||
var ggResults = [];
|
||||
|
||||
$(function () {
|
||||
var msg = i18nMsg;
|
||||
/*var douban = "https://api.douban.com";
|
||||
var dbSearch = "/v2/book/search";*/
|
||||
var dbDone = true;
|
||||
// var dbDone = true;
|
||||
|
||||
var google = "https://www.googleapis.com/";
|
||||
var google = "https://www.googleapis.com";
|
||||
var ggSearch = "/books/v1/volumes";
|
||||
var ggDone = false;
|
||||
|
||||
@@ -56,11 +56,9 @@ $(function () {
|
||||
if (showFlag === 1) {
|
||||
$("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
|
||||
}
|
||||
if (ggDone && dbDone) {
|
||||
if (!ggResults && !dbResults) {
|
||||
$("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>");
|
||||
return;
|
||||
}
|
||||
if (!ggDone) {
|
||||
$("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>");
|
||||
return;
|
||||
}
|
||||
if (ggDone && ggResults.length > 0) {
|
||||
ggResults.forEach(function(result) {
|
||||
@@ -137,10 +135,12 @@ $(function () {
|
||||
dataType: "jsonp",
|
||||
jsonp: "callback",
|
||||
success: function success(data) {
|
||||
ggResults = data.items;
|
||||
if ("items" in data) {
|
||||
ggResults = data.items;
|
||||
ggDone = true;
|
||||
}
|
||||
},
|
||||
complete: function complete() {
|
||||
ggDone = true;
|
||||
showResult();
|
||||
$("#show-google").trigger("change");
|
||||
}
|
||||
|
||||
@@ -1,483 +0,0 @@
|
||||
/*
|
||||
* io.js
|
||||
*
|
||||
* Provides readers for bit/byte streams (reading) and a byte buffer (writing).
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2011 Google Inc.
|
||||
* Copyright(c) 2011 antimatter15
|
||||
*/
|
||||
|
||||
/* global bitjs, Uint8Array */
|
||||
|
||||
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} optOffset The offset into the ArrayBuffer
|
||||
* @param {Number} optLength The length of this BitStream
|
||||
*/
|
||||
bitjs.io.BitStream = function(ab, rtl, optOffset, optLength) {
|
||||
if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") {
|
||||
throw "Error! BitArray constructed with an invalid ArrayBuffer object";
|
||||
}
|
||||
|
||||
var offset = optOffset || 0;
|
||||
var length = optLength || 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.peekBitsRtl : this.peekBitsLtr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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.peekBitsLtr = function(n, movePointers) {
|
||||
if (n <= 0 || typeof n !== typeof 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var movePointers = movePointers || false;
|
||||
var bytePtr = this.bytePtr;
|
||||
var bitPtr = this.bitPtr;
|
||||
var result = 0;
|
||||
var bitsIn = 0;
|
||||
var 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;
|
||||
}
|
||||
|
||||
var numBitsLeftInThisByte = (8 - bitPtr);
|
||||
var mask;
|
||||
if (n >= numBitsLeftInThisByte) {
|
||||
mask = (BITMASK[numBitsLeftInThisByte] << bitPtr);
|
||||
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
|
||||
|
||||
bytePtr++;
|
||||
bitPtr = 0;
|
||||
bitsIn += numBitsLeftInThisByte;
|
||||
n -= numBitsLeftInThisByte;
|
||||
} else {
|
||||
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.peekBitsRtl = function(n, movePointers) {
|
||||
if (n <= 0 || typeof n !== typeof 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var movePointers = movePointers || false;
|
||||
var bytePtr = this.bytePtr;
|
||||
var bitPtr = this.bitPtr;
|
||||
var result = 0;
|
||||
var 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;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Some voodoo magic.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
movePointers = movePointers || false;
|
||||
var bytePtr = this.bytePtr;
|
||||
// var 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);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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=} optOffset The offset into the ArrayBuffer
|
||||
* @param {number=} optLength The length of this BitStream
|
||||
* @constructor
|
||||
*/
|
||||
bitjs.io.ByteStream = function(ab, optOffset, optLength) {
|
||||
var offset = optOffset || 0;
|
||||
var length = optLength || 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;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
})();
|
||||
Executable
+237
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* bitstream.js
|
||||
*
|
||||
* Provides readers for bitstreams.
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2011 Google Inc.
|
||||
* Copyright(c) 2011 antimatter15
|
||||
*/
|
||||
|
||||
/* global bitjs, Uint8Array */
|
||||
|
||||
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} optOffset The offset into the ArrayBuffer
|
||||
* @param {Number} optLength The length of this BitStream
|
||||
*/
|
||||
bitjs.io.BitStream = function(ab, rtl, optOffset, optLength) {
|
||||
if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") {
|
||||
throw "Error! BitArray constructed with an invalid ArrayBuffer object";
|
||||
}
|
||||
|
||||
var offset = optOffset || 0;
|
||||
var length = optLength || 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.peekBitsRtl : this.peekBitsLtr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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.peekBitsLtr = 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);
|
||||
var mask;
|
||||
if (n >= numBitsLeftInThisByte) {
|
||||
mask = (BITMASK[numBitsLeftInThisByte] << bitPtr);
|
||||
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
|
||||
|
||||
bytePtr++;
|
||||
bitPtr = 0;
|
||||
bitsIn += numBitsLeftInThisByte;
|
||||
n -= numBitsLeftInThisByte;
|
||||
} else {
|
||||
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.peekBitsRtl = 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,124 @@
|
||||
/*
|
||||
* bytestream.js
|
||||
*
|
||||
* Provides a writer for bytes.
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2011 Google Inc.
|
||||
* Copyright(c) 2011 antimatter15
|
||||
*/
|
||||
|
||||
/* global bitjs, Uint8Array */
|
||||
|
||||
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,166 @@
|
||||
/*
|
||||
* bytestream.js
|
||||
*
|
||||
* Provides readers for byte streams.
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2011 Google Inc.
|
||||
* Copyright(c) 2011 antimatter15
|
||||
*/
|
||||
|
||||
/* global bitjs, Uint8Array */
|
||||
|
||||
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=} optOffset The offset into the ArrayBuffer
|
||||
* @param {number=} optLength The length of this BitStream
|
||||
* @constructor
|
||||
*/
|
||||
bitjs.io.ByteStream = function(ab, optOffset, optLength) {
|
||||
var offset = optOffset || 0;
|
||||
var length = optLength || 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;
|
||||
};
|
||||
|
||||
})();
|
||||
+40
-29
@@ -15,7 +15,9 @@
|
||||
* Typed Arrays: http://www.khronos.org/registry/typedarray/specs/latest/#6
|
||||
|
||||
*/
|
||||
/* global screenfull, bitjs */
|
||||
/* global screenfull, bitjs, Uint8Array, opera */
|
||||
/* exported init, event */
|
||||
|
||||
|
||||
if (window.opera) {
|
||||
window.console.log = function(str) {
|
||||
@@ -102,12 +104,12 @@ kthoom.setSettings = function() {
|
||||
var createURLFromArray = function(array, mimeType) {
|
||||
var offset = array.byteOffset;
|
||||
var len = array.byteLength;
|
||||
var url;
|
||||
// var url;
|
||||
var blob;
|
||||
|
||||
if (mimeType === 'image/xml+svg') {
|
||||
const xmlStr = new TextDecoder('utf-8').decode(array);
|
||||
return 'data:image/svg+xml;UTF-8,' + encodeURIComponent(xmlStr);
|
||||
if (mimeType === "image/xml+svg") {
|
||||
var xmlStr = new TextDecoder("utf-8").decode(array);
|
||||
return "data:image/svg+xml;UTF-8," + encodeURIComponent(xmlStr);
|
||||
}
|
||||
|
||||
// TODO: Move all this browser support testing to a common place
|
||||
@@ -141,7 +143,7 @@ kthoom.ImageFile = function(file) {
|
||||
var fileExtension = file.filename.split(".").pop().toLowerCase();
|
||||
this.mimeType = fileExtension === "png" ? "image/png" :
|
||||
(fileExtension === "jpg" || fileExtension === "jpeg") ? "image/jpeg" :
|
||||
fileExtension === "gif" ? "image/gif" : fileExtension == 'svg' ? 'image/xml+svg' : undefined;
|
||||
fileExtension === "gif" ? "image/gif" : fileExtension === "svg" ? "image/xml+svg" : undefined;
|
||||
if ( this.mimeType !== undefined) {
|
||||
this.dataURI = createURLFromArray(file.fileData, this.mimeType);
|
||||
this.data = file;
|
||||
@@ -155,16 +157,22 @@ function initProgressClick() {
|
||||
currentImage = page;
|
||||
updatePage();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function loadFromArrayBuffer(ab) {
|
||||
var start = (new Date).getTime();
|
||||
var h = new Uint8Array(ab, 0, 10);
|
||||
var pathToBitJS = "../../static/js/";
|
||||
var pathToBitJS = "../../static/js/archive/";
|
||||
var lastCompletion = 0;
|
||||
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 if (h[0] === 255 && h[1] === 216) { // JPEG
|
||||
// ToDo: check
|
||||
updateProgress(100);
|
||||
lastCompletion = 100;
|
||||
return;
|
||||
} else { // Try with tar
|
||||
unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS);
|
||||
}
|
||||
@@ -176,9 +184,13 @@ function loadFromArrayBuffer(ab) {
|
||||
if (totalImages === 0) {
|
||||
totalImages = e.totalFilesInArchive;
|
||||
}
|
||||
updateProgress(percentage *100);
|
||||
updateProgress(percentage * 100);
|
||||
lastCompletion = percentage * 100;
|
||||
});
|
||||
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.INFO,
|
||||
function(e) {
|
||||
// console.log(e.msg); // Enable debug output here
|
||||
});
|
||||
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT,
|
||||
function(e) {
|
||||
// convert DecompressedFile into a bunch of ImageFiles
|
||||
@@ -203,8 +215,7 @@ function loadFromArrayBuffer(ab) {
|
||||
if (imageFiles.length === currentImage + 1) {
|
||||
updatePage(lastCompletion);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
totalImages--;
|
||||
}
|
||||
}
|
||||
@@ -223,22 +234,22 @@ function loadFromArrayBuffer(ab) {
|
||||
|
||||
function scrollTocToActive() {
|
||||
// Scroll to the thumbnail in the TOC on page change
|
||||
$('#tocView').stop().animate({
|
||||
scrollTop: $('#tocView a.active').position().top
|
||||
$("#tocView").stop().animate({
|
||||
scrollTop: $("#tocView a.active").position().top
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function updatePage() {
|
||||
$('.page').text((currentImage + 1 ) + "/" + totalImages);
|
||||
$(".page").text((currentImage + 1 ) + "/" + totalImages);
|
||||
|
||||
// Mark the current page in the TOC
|
||||
$('#tocView a[data-page]')
|
||||
$("#tocView a[data-page]")
|
||||
// Remove the currently active thumbnail
|
||||
.removeClass('active')
|
||||
.removeClass("active")
|
||||
// Find the new one
|
||||
.filter('[data-page='+ (currentImage + 1) +']')
|
||||
.filter("[data-page=" + (currentImage + 1) + "]")
|
||||
// Set it to active
|
||||
.addClass('active');
|
||||
.addClass("active");
|
||||
|
||||
scrollTocToActive();
|
||||
updateProgress();
|
||||
@@ -262,8 +273,8 @@ function updateProgress(loadPercentage) {
|
||||
|
||||
if (loadPercentage === 100) {
|
||||
$("#progress")
|
||||
.removeClass('loading')
|
||||
.find(".load").text('');
|
||||
.removeClass("loading")
|
||||
.find(".load").text("");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +329,7 @@ function setImage(url) {
|
||||
xhr.onload = function() {
|
||||
$("#mainText").css("display", "");
|
||||
$("#mainText").innerHTML("<iframe style=\"width:100%;height:700px;border:0\" src=\"data:text/html," + escape(xhr.responseText) + "\"></iframe>");
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
} else if (!/(jpg|jpeg|png|gif)$/.test(imageFiles[currentImage].filename) && imageFiles[currentImage].data.uncompressedSize < 10 * 1024) {
|
||||
xhr.open("GET", url, true);
|
||||
@@ -370,17 +381,17 @@ function setImage(url) {
|
||||
|
||||
function showLeftPage() {
|
||||
if (settings.direction === 0) {
|
||||
showPrevPage()
|
||||
showPrevPage();
|
||||
} else {
|
||||
showNextPage()
|
||||
showNextPage();
|
||||
}
|
||||
}
|
||||
|
||||
function showRightPage() {
|
||||
if (settings.direction === 0) {
|
||||
showNextPage()
|
||||
showNextPage();
|
||||
} else {
|
||||
showPrevPage()
|
||||
showPrevPage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,7 +507,7 @@ function keyHandler(evt) {
|
||||
updateScale(false);
|
||||
break;
|
||||
case kthoom.Key.SPACE:
|
||||
var container = $('#mainContent');
|
||||
var container = $("#mainContent");
|
||||
var atTop = container.scrollTop() === 0;
|
||||
var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height();
|
||||
|
||||
@@ -569,9 +580,9 @@ function init(filename) {
|
||||
$(this).toggleClass("icon-menu icon-right");
|
||||
|
||||
// We need this in a timeout because if we call it during the CSS transition, IE11 shakes the page ¯\_(ツ)_/¯
|
||||
setTimeout(function(){
|
||||
setTimeout(function() {
|
||||
// Focus on the TOC or the main content area, depending on which is open
|
||||
$('#main:not(.closed) #mainContent, #sidebar.open #tocView').focus();
|
||||
$("#main:not(.closed) #mainContent, #sidebar.open #tocView").focus();
|
||||
scrollTocToActive();
|
||||
}, 500);
|
||||
});
|
||||
@@ -622,7 +633,7 @@ function init(filename) {
|
||||
}
|
||||
|
||||
// Focus the scrollable area so that keyboard scrolling work as expected
|
||||
$('#mainContent').focus();
|
||||
$("#mainContent").focus();
|
||||
|
||||
$("#mainImage").click(function(evt) {
|
||||
// Firefox does not support offsetX/Y so we have to manually calculate
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -150,9 +150,9 @@
|
||||
{% endif %}
|
||||
{% if cc|length > 0 %}
|
||||
|
||||
<div class="custom_columns">
|
||||
<p>
|
||||
|
||||
{% for c in cc %}
|
||||
<div class="real_custom_columns">
|
||||
{% if entry['custom_column_' ~ c.id]|length > 0 %}
|
||||
{{ c.name }}:
|
||||
{% for column in entry['custom_column_' ~ c.id] %}
|
||||
@@ -170,11 +170,9 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<br />
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if not g.user.is_anonymous %}
|
||||
|
||||
@@ -193,7 +191,7 @@
|
||||
|
||||
{% if entry.comments|length > 0 and entry.comments[0].text|length > 0%}
|
||||
<div class="comments">
|
||||
<h3>{{_('Description:')}}</h3>
|
||||
<h3 id="decription">{{_('Description:')}}</h3>
|
||||
{{entry.comments[0].text|safe}}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Comic Reader</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Comic Reader</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
@@ -15,7 +15,7 @@
|
||||
<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/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>
|
||||
var updateArrows = function() {
|
||||
if ($('input[name="direction"]:checked').val() === "0") {
|
||||
@@ -31,17 +31,11 @@
|
||||
init("{{ url_for('serve_book', book_id=comicfile, book_format=extension) }}");
|
||||
updateArrows();
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<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">
|
||||
<ul id="thumbnails"></ul>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ from werkzeug.security import generate_password_hash
|
||||
import json
|
||||
import datetime
|
||||
from binascii import hexlify
|
||||
import cli
|
||||
from cps import cli
|
||||
|
||||
engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False)
|
||||
Base = declarative_base()
|
||||
@@ -179,6 +179,7 @@ class UserBase:
|
||||
# User Base (all access methods are declared there)
|
||||
class User(UserBase, Base):
|
||||
__tablename__ = 'user'
|
||||
__table_args__ = {'sqlite_autoincrement': True}
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
nickname = Column(String(64), unique=True)
|
||||
@@ -715,7 +716,34 @@ def migrate_Database():
|
||||
conn = engine.connect()
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_updatechannel` INTEGER DEFAULT 0")
|
||||
session.commit()
|
||||
|
||||
try:
|
||||
# check if one table with autoincrement is existing (should be user table)
|
||||
conn = engine.connect()
|
||||
conn.execute("SELECT COUNT(*) FROM sqlite_sequence WHERE name='user'")
|
||||
except exc.OperationalError:
|
||||
# Create new table user_id and copy contents of table user into it
|
||||
conn = engine.connect()
|
||||
conn.execute("CREATE TABLE user_id (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
|
||||
"nickname VARCHAR(64),"
|
||||
"email VARCHAR(120),"
|
||||
"role SMALLINT,"
|
||||
"password VARCHAR,"
|
||||
"kindle_mail VARCHAR(120),"
|
||||
"locale VARCHAR(2),"
|
||||
"sidebar_view INTEGER,"
|
||||
"default_language VARCHAR(3),"
|
||||
"mature_content BOOLEAN,"
|
||||
"UNIQUE (nickname),"
|
||||
"UNIQUE (email),"
|
||||
"CHECK (mature_content IN (0, 1)))")
|
||||
conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale,"
|
||||
"sidebar_view, default_language, mature_content) "
|
||||
"SELECT id, nickname, email, role, password, kindle_mail, locale,"
|
||||
"sidebar_view, default_language, mature_content FROM user")
|
||||
# delete old user table and rename new user_id table to user:
|
||||
conn.execute("DROP TABLE user")
|
||||
conn.execute("ALTER TABLE user_id RENAME TO user")
|
||||
session.commit()
|
||||
|
||||
# Remove login capability of user Guest
|
||||
conn = engine.connect()
|
||||
|
||||
+15
-8
@@ -237,7 +237,7 @@ class Updater(threading.Thread):
|
||||
return False
|
||||
|
||||
def _stable_version_info(self):
|
||||
return {'version': '0.6.3'} # Current version
|
||||
return {'version': '0.6.4'} # Current version
|
||||
|
||||
def _nightly_available_updates(self, request_method):
|
||||
tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)
|
||||
@@ -426,18 +426,25 @@ class Updater(threading.Thread):
|
||||
minor_version_update = int(commit[i]['tag_name'].split('.')[1])
|
||||
patch_version_update = int(commit[i]['tag_name'].split('.')[2])
|
||||
|
||||
current_version[0] = int(current_version[0])
|
||||
current_version[1] = int(current_version[1])
|
||||
try:
|
||||
current_version[2] = int(current_version[2])
|
||||
except ValueError:
|
||||
current_version[2] = int(current_version[2].split(' ')[0])-1
|
||||
|
||||
# Check if major versions are identical search for newest nonenqual commit and update to this one
|
||||
if major_version_update == int(current_version[0]):
|
||||
if (minor_version_update == int(current_version[1]) and
|
||||
patch_version_update > int(current_version[2])) or \
|
||||
minor_version_update > int(current_version[1]):
|
||||
if major_version_update == current_version[0]:
|
||||
if (minor_version_update == current_version[1] and
|
||||
patch_version_update > current_version[2]) or \
|
||||
minor_version_update > current_version[1]:
|
||||
parents.append([commit[i]['tag_name'],commit[i]['body'].replace('\r\n', '<p>')])
|
||||
i -= 1
|
||||
continue
|
||||
if major_version_update < int(current_version[0]):
|
||||
if major_version_update < current_version[0]:
|
||||
i -= 1
|
||||
continue
|
||||
if major_version_update > int(current_version[0]):
|
||||
if major_version_update > current_version[0]:
|
||||
# found update update to last version before major update, unless current version is on last version
|
||||
# before major update
|
||||
if commit[i+1]['tag_name'].split('.')[1] == current_version[1]:
|
||||
@@ -466,7 +473,7 @@ class Updater(threading.Thread):
|
||||
'update': True,
|
||||
'success': True,
|
||||
'message': _(
|
||||
u'A new update is available. Click on the button below to update to the latest version.'),
|
||||
u'Click on the button below to update to the latest stable version.'),
|
||||
'history': parents
|
||||
})
|
||||
self.updateFile = commit[0]['zipball_url']
|
||||
|
||||
+38
-13
@@ -112,7 +112,7 @@ try:
|
||||
from natsort import natsorted as sort
|
||||
except ImportError:
|
||||
sort=sorted # Just use regular sort then
|
||||
# may cause issues with badly named pages in cbz/cbr files
|
||||
# may cause issues with badly named pages in cbz/cbr files
|
||||
try:
|
||||
import cPickle
|
||||
except ImportError:
|
||||
@@ -3171,13 +3171,22 @@ def new_user():
|
||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||
title=_(u"Add new user"))
|
||||
content.password = generate_password_hash(to_save["password"])
|
||||
content.nickname = to_save["nickname"]
|
||||
if config.config_public_reg and not check_valid_domain(to_save["email"]):
|
||||
flash(_(u"E-mail is not from valid domain"), category="error")
|
||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||
title=_(u"Add new user"))
|
||||
existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == to_save["nickname"].lower())\
|
||||
.first()
|
||||
existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower())\
|
||||
.first()
|
||||
if not existing_user and not existing_email:
|
||||
content.nickname = to_save["nickname"]
|
||||
if config.config_public_reg and not check_valid_domain(to_save["email"]):
|
||||
flash(_(u"E-mail is not from valid domain"), category="error")
|
||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||
title=_(u"Add new user"))
|
||||
else:
|
||||
content.email = to_save["email"]
|
||||
else:
|
||||
content.email = to_save["email"]
|
||||
flash(_(u"Found an existing account for this e-mail address or nickname."), category="error")
|
||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||
languages=languages, title=_(u"Add new user"), page="newuser")
|
||||
try:
|
||||
ub.session.add(content)
|
||||
ub.session.commit()
|
||||
@@ -3246,10 +3255,16 @@ def edit_user(user_id):
|
||||
if request.method == "POST":
|
||||
to_save = request.form.to_dict()
|
||||
if "delete" in to_save:
|
||||
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
||||
ub.session.commit()
|
||||
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
|
||||
return redirect(url_for('admin'))
|
||||
if ub.session.query(ub.User).filter(and_(ub.User.role.op('&')
|
||||
(ub.ROLE_ADMIN)== ub.ROLE_ADMIN,
|
||||
ub.User.id != content.id)).count():
|
||||
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
||||
ub.session.commit()
|
||||
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
|
||||
return redirect(url_for('admin'))
|
||||
else:
|
||||
flash(_(u"No admin user remaining, can't delete user", nick=content.nickname), category="error")
|
||||
return redirect(url_for('admin'))
|
||||
else:
|
||||
if "password" in to_save and to_save["password"]:
|
||||
content.password = generate_password_hash(to_save["password"])
|
||||
@@ -3356,14 +3371,24 @@ def edit_user(user_id):
|
||||
if "locale" in to_save and to_save["locale"]:
|
||||
content.locale = to_save["locale"]
|
||||
if to_save["email"] and to_save["email"] != content.email:
|
||||
content.email = to_save["email"]
|
||||
existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()) \
|
||||
.first()
|
||||
if not existing_email:
|
||||
content.email = to_save["email"]
|
||||
else:
|
||||
flash(_(u"Found an existing account for this e-mail address."), category="error")
|
||||
return render_title_template("user_edit.html", translations=translations, languages=languages,
|
||||
new_user=0, content=content, downloads=downloads,
|
||||
title=_(u"Edit User %(nick)s", nick=content.nickname), page="edituser")
|
||||
|
||||
if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail:
|
||||
content.kindle_mail = to_save["kindle_mail"]
|
||||
try:
|
||||
ub.session.commit()
|
||||
flash(_(u"User '%(nick)s' updated", nick=content.nickname), category="success")
|
||||
except IntegrityError:
|
||||
except IntegrityError as e:
|
||||
ub.session.rollback()
|
||||
print(e)
|
||||
flash(_(u"An unknown error occured."), category="error")
|
||||
return render_title_template("user_edit.html", translations=translations, languages=languages, new_user=0,
|
||||
content=content, downloads=downloads, title=_(u"Edit User %(nick)s",
|
||||
|
||||
+15
-21
@@ -20,7 +20,7 @@
|
||||
from __future__ import print_function
|
||||
import smtplib
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import time
|
||||
import socket
|
||||
@@ -221,8 +221,10 @@ class WorkerThread(threading.Thread):
|
||||
if self.UIqueue[self.current]['stat'] == STAT_STARTED:
|
||||
if self.queue[self.current]['taskType'] == TASK_EMAIL:
|
||||
self.UIqueue[self.current]['progress'] = self.get_send_status()
|
||||
self.UIqueue[self.current]['runtime'] = self._formatRuntime(
|
||||
datetime.now() - self.queue[self.current]['starttime'])
|
||||
self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
|
||||
self.UIqueue[self.current]['rt'] = self.UIqueue[self.current]['formRuntime'].days*24*60 \
|
||||
+ self.UIqueue[self.current]['formRuntime'].seconds \
|
||||
+ self.UIqueue[self.current]['formRuntime'].microseconds
|
||||
return self.UIqueue
|
||||
|
||||
def _convert_any_format(self):
|
||||
@@ -259,7 +261,8 @@ class WorkerThread(threading.Thread):
|
||||
self._handleSuccess()
|
||||
return file_path + format_new_ext
|
||||
else:
|
||||
web.app.logger.info("Book id %d - target format of %s does not exist. Moving forward with convert.", bookid, format_new_ext)
|
||||
web.app.logger.info("Book id %d - target format of %s does not exist. Moving forward with convert.",
|
||||
bookid, format_new_ext)
|
||||
|
||||
# check if converter-executable is existing
|
||||
if not os.path.exists(web.ub.config.config_converterpath):
|
||||
@@ -300,7 +303,7 @@ class WorkerThread(threading.Thread):
|
||||
if sys.version_info < (3, 0):
|
||||
command = [x.encode(sys.getfilesystemencoding()) for x in command]
|
||||
|
||||
p = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True)
|
||||
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
|
||||
except OSError as e:
|
||||
self._handleError(_(u"Ebook-converter failed: %(error)s", error=e))
|
||||
return
|
||||
@@ -328,6 +331,11 @@ class WorkerThread(threading.Thread):
|
||||
|
||||
# process returncode
|
||||
check = p.returncode
|
||||
calibre_traceback = p.stderr.readlines()
|
||||
for ele in calibre_traceback:
|
||||
web.app.logger.debug(ele.strip('\n'))
|
||||
if not ele.startswith('Traceback') and not ele.startswith(' File'):
|
||||
error_message = "Calibre failed with error: %s" % ele.strip('\n')
|
||||
|
||||
# kindlegen returncodes
|
||||
# 0 = Info(prcgen):I1036: Mobi file built successfully
|
||||
@@ -481,31 +489,17 @@ class WorkerThread(threading.Thread):
|
||||
self._handleError(u'Error sending email: ' + e.strerror)
|
||||
return None
|
||||
|
||||
def _formatRuntime(self, runtime):
|
||||
self.UIqueue[self.current]['rt'] = runtime.total_seconds()
|
||||
val = re.split('\:|\.', str(runtime))[0:3]
|
||||
erg = list()
|
||||
for v in val:
|
||||
if int(v) > 0:
|
||||
erg.append(v)
|
||||
retVal = (':'.join(erg)).lstrip('0') + ' s'
|
||||
if retVal == ' s':
|
||||
retVal = '0 s'
|
||||
return retVal
|
||||
|
||||
def _handleError(self, error_message):
|
||||
web.app.logger.error(error_message)
|
||||
self.UIqueue[self.current]['stat'] = STAT_FAIL
|
||||
self.UIqueue[self.current]['progress'] = "100 %"
|
||||
self.UIqueue[self.current]['runtime'] = self._formatRuntime(
|
||||
datetime.now() - self.queue[self.current]['starttime'])
|
||||
self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
|
||||
self.UIqueue[self.current]['message'] = error_message
|
||||
|
||||
def _handleSuccess(self):
|
||||
self.UIqueue[self.current]['stat'] = STAT_FINISH_SUCCESS
|
||||
self.UIqueue[self.current]['progress'] = "100 %"
|
||||
self.UIqueue[self.current]['runtime'] = self._formatRuntime(
|
||||
datetime.now() - self.queue[self.current]['starttime'])
|
||||
self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
|
||||
|
||||
|
||||
# Enable logging of smtp lib debug output
|
||||
|
||||
@@ -20,4 +20,4 @@ Pillow>=4.0.0
|
||||
rarfile>=2.7
|
||||
# other
|
||||
natsort>=2.2.0
|
||||
|
||||
git+https://github.com/OzzieIsaacs/comicapi.git@5346716578b2843f54d522f44d01bc8d25001d24#egg=comicapi
|
||||
|
||||
@@ -56,20 +56,30 @@ Optionally, to enable on-the-fly conversion from one ebook format to another whe
|
||||
|
||||
[Download](http://www.amazon.com/gp/feature.html?docId=1000765211) Amazon's KindleGen tool for your platform and place the binary named as `kindlegen` in the `vendor` folder.
|
||||
|
||||
## Docker images
|
||||
## Docker Images
|
||||
|
||||
Pre-built Docker images based on Alpine Linux are available in these Docker Hub repositories:
|
||||
Pre-built Docker images are available in these Docker Hub repositories:
|
||||
|
||||
**x64**
|
||||
+ **technosoft2000** at [technosoft2000/calibre-web](https://hub.docker.com/r/technosoft2000/calibre-web/). If you want the option to convert/download ebooks in multiple formats, use this image as it includes Calibre's ebook-convert binary. The "path to convertertool" should be set to /opt/calibre/ebook-convert.
|
||||
+ **linuxserver.io** at [linuxserver/calibre-web](https://hub.docker.com/r/linuxserver/calibre-web/). Cannot convert between ebook formats.
|
||||
#### **Technosoft2000 - x64**
|
||||
+ Docker Hub - [https://hub.docker.com/r/technosoft2000/calibre-web/](https://hub.docker.com/r/technosoft2000/calibre-web/)
|
||||
+ Github - [https://github.com/Technosoft2000/docker-calibre-web](https://github.com/Technosoft2000/docker-calibre-web)
|
||||
|
||||
**armhf**
|
||||
+ **linuxserver.io** at [lsioarmhf/calibre-web](https://hub.docker.com/r/lsioarmhf/calibre-web/)
|
||||
Includes the Calibre `ebook-convert` binary.
|
||||
+ The "path to convertertool" should be set to `/opt/calibre/ebook-convert`
|
||||
|
||||
**aarch64**
|
||||
+ **linuxserver.io** at [lsioarmhf/calibre-web-aarch64](https://hub.docker.com/r/lsioarmhf/calibre-web-aarch64)
|
||||
#### **LinuxServer - x64, armhf, aarch64**
|
||||
+ Docker Hub - [https://hub.docker.com/r/linuxserver/calibre-web/](https://hub.docker.com/r/linuxserver/calibre-web/)
|
||||
+ Github - [https://github.com/linuxserver/docker-calibre-web](https://github.com/linuxserver/docker-calibre-web)
|
||||
+ Github - (Optional Calibre layer) - [https://github.com/linuxserver/docker-calibre-web/tree/calibre](https://github.com/linuxserver/docker-calibre-web/tree/calibre)
|
||||
|
||||
This image has the option to pull in an extra docker manifest layer to include the Calibre `ebook-convert` binary. Just include the environmental variable `DOCKER_MODS=linuxserver/calibre-web:calibre` in your docker run/docker compose file. **(x64 only)**
|
||||
|
||||
If you do not need this functionality then this can be omitted, keeping the image as lightweight as possible.
|
||||
|
||||
Both the Calibre-Web and Calibre-Mod images are rebuilt automatically on new releases of Calibre-Web and Calibre respectively, and on updates to any included base image packages on a weekly basis if required.
|
||||
+ The "path to convertertool" should be set to `/usr/bin/ebook-convert`
|
||||
+ The "path to unrar" should be set to `/usr/bin/unrar`
|
||||
|
||||
# Wiki
|
||||
|
||||
For further informations, How To's and FAQ please check the 
|
||||
For further informations, How To's and FAQ please check the [Wiki](../../wiki)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[metadata]
|
||||
name = calibre-web
|
||||
url = https://github.com/janeczku/calibre-web
|
||||
project_urls =
|
||||
Bug Tracker = https://github.com/janeczku/calibre-web/issues
|
||||
Release Management = https://github.com/janeczku/calibre-web/releases
|
||||
Documentation = https://github.com/janeczku/calibre-web/wiki
|
||||
Source Code = https://github.com/janeczku/calibre-web
|
||||
description = Web app for browsing, reading and downloading eBooks stored in a Calibre database.
|
||||
long_description = file: README.md
|
||||
author = @janeczku
|
||||
maintainer = @janeczku
|
||||
license = GPLv3+
|
||||
license_file = LICENSE
|
||||
classifiers =
|
||||
Development Status :: 5 - Production/Stable
|
||||
License :: OSI Approved :: GNU Affero General Public License v3
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.5
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
keywords =
|
||||
calibre
|
||||
calibre-web
|
||||
library
|
||||
|
||||
[options]
|
||||
python_requires = >=2.6
|
||||
packages = find:
|
||||
include_package_data = True
|
||||
zip_safe = False
|
||||
install_requires =
|
||||
Babel >= 1.3
|
||||
Flask-Babel >= 0.11.1
|
||||
Flask-Login >= 0.3.2
|
||||
Flask-Principal >= 0.3.2
|
||||
singledispatch >= 3.4.0.0
|
||||
backports_abc >= 0.4
|
||||
Flask >= 0.11
|
||||
iso-639 >= 0.4.5
|
||||
PyPDF2 == 1.26.0
|
||||
pytz >= 2016.10
|
||||
requests >= 2.11.1
|
||||
SQLAlchemy >= 1.1.0
|
||||
tornado >= 4.1
|
||||
unidecode >= 0.04.19
|
||||
|
||||
[options.extras_require]
|
||||
gdrive =
|
||||
google-api-python-client == 1.6.1
|
||||
google-api-python-client==1.6.1
|
||||
gevent==1.2.1
|
||||
greenlet==0.4.12
|
||||
httplib2==0.9.2
|
||||
oauth2client==4.0.0
|
||||
uritemplate==3.0.0
|
||||
pyasn1-modules==0.0.8
|
||||
pyasn1==0.1.9
|
||||
PyDrive==1.3.1
|
||||
PyYAML==3.12
|
||||
rsa==3.4.2
|
||||
six==1.10.0
|
||||
goodreads =
|
||||
goodreads >= 0.3.2
|
||||
python-Levenshtein>=0.12.0
|
||||
metadata =
|
||||
lxml>=3.8.0
|
||||
Pillow>=4.0.0
|
||||
rarfile>=2.7
|
||||
Wand >= 0.4.4
|
||||
comics=
|
||||
natsort>=2.2.0
|
||||
comicapi @ git+https://github.com/OzzieIsaacs/comicapi.git@5346716578b2843f54d522f44d01bc8d25001d24#egg=comicapi
|
||||
|
||||
[options.packages.find]
|
||||
where = .
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||
# Copyright (C) 2019 decentral1se
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# """Calibre-web distribution package setuptools installer."""
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
||||
Reference in New Issue
Block a user