// ==UserScript==
// @name E HENTAI VIEW ENHANCE
// @name:zh-CN E绅士阅读强化
// @namespace https://github.com/MapoMagpie/eh-view-enhance
// @version 4.4.15
// @author MapoMagpie
// @description Manga Viewer + Downloader, Focus on experience and low load on the site. Support: e-hentai.org | exhentai.org | pixiv.net | 18comic.vip | nhentai.net | hitomi.la | rule34.xxx | danbooru.donmai.us | gelbooru.com
// @description:zh-CN 漫画阅读 + 下载器,注重体验和对站点的负载控制。支持:e-hentai.org | exhentai.org | pixiv.net | 18comic.vip | nhentai.net | hitomi.la | rule34.xxx | danbooru.donmai.us | gelbooru.com
// @license MIT
// @icon https://exhentai.org/favicon.ico
// @supportURL https://github.com/MapoMagpie/eh-view-enhance/issues
// @match https://exhentai.org/*
// @match https://e-hentai.org/*
// @match http://exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion/*
// @match https://nhentai.net/*
// @match https://steamcommunity.com/id/*/screenshots*
// @match https://hitomi.la/*
// @match https://www.pixiv.net/*
// @match https://yande.re/*
// @match https://rokuhentai.com/*
// @match https://18comic.org/*
// @match https://18comic.vip/*
// @match https://18-comicfreedom.xyz/*
// @match https://rule34.xxx/*
// @match https://imhentai.xxx/*
// @match https://danbooru.donmai.us/*
// @match https://gelbooru.com/*
// @connect exhentai.org
// @connect e-hentai.org
// @connect hath.network
// @connect exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion
// @connect nhentai.net
// @connect hitomi.la
// @connect akamaihd.net
// @connect i.pximg.net
// @connect ehgt.org
// @connect yande.re
// @connect 18comic.org
// @connect 18comic.vip
// @connect 18-comicfreedom.xyz
// @connect rule34.xxx
// @connect imhentai.xxx
// @connect donmai.us
// @connect gelbooru.com
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
// src/native/alias.ts
var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
function defaultConf() {
const screenWidth = window.screen.width;
const colCount = screenWidth > 2500 ? 7 : screenWidth > 1900 ? 6 : 5;
return {
backgroundImage: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAfElEQVR42mP8z/CfCgIwDEgwAIAL0Fq3MDD5iQcn0/BgDpDAn0/AvywA4kUEZ7gUkXBoAM5gUQUaJ6eClOyBjALcAAAAASUVORK5CYII=`,
colCount,
readMode: "pagination",
autoLoad: true,
fetchOriginal: false,
restartIdleLoader: 2e3,
threads: 3,
downloadThreads: 4,
timeout: 10,
version: VERSION,
debug: true,
first: true,
reversePages: false,
pageHelperAbTop: "unset",
pageHelperAbLeft: "20px",
pageHelperAbBottom: "20px",
pageHelperAbRight: "unset",
imgScale: 0,
stickyMouse: "disable",
autoPageInterval: 5e3,
autoPlay: false,
filenameTemplate: "{number}-{title}",
preventScrollPageTime: 100,
archiveVolumeSize: 1200,
convertTo: "GIF",
autoCollapsePanel: true,
minifyPageHelper: "inBigMode",
keyboards: { inBigImageMode: {}, inFullViewGrid: {}, inMain: {} },
excludeURLs: [],
muted: false,
volume: 50,
disableCssAnimation: true,
mcInSites: ["18comic"],
paginationIMGCount: 1,
hitomiFormat: "auto",
autoOpen: false,
autoLoadInBackground: true
};
}
const VERSION = "4.4.0";
const CONFIG_KEY = "ehvh_cfg_";
function getStorageMethod() {
if (typeof _GM_getValue === "function" && typeof _GM_setValue === "function") {
return {
setItem: (key, value) => _GM_setValue(key, value),
getItem: (key) => _GM_getValue(key)
};
} else if (typeof localStorage !== "undefined") {
return {
setItem: (key, value) => localStorage.setItem(key, value),
getItem: (key) => localStorage.getItem(key)
};
} else {
throw new Error("No supported storage method found");
}
}
const storage = getStorageMethod();
function getConf() {
let cfgStr = storage.getItem(CONFIG_KEY);
if (cfgStr) {
let cfg2 = JSON.parse(cfgStr);
if (cfg2.version === VERSION) {
return confHealthCheck(cfg2);
}
}
let cfg = defaultConf();
saveConf(cfg);
return cfg;
}
function confHealthCheck(cf) {
let changed = false;
const defa = defaultConf();
const keys = Object.keys(defa);
keys.forEach((key) => {
if (cf[key] === void 0) {
cf[key] = defa[key];
changed = true;
}
});
["pageHelperAbTop", "pageHelperAbLeft", "pageHelperAbBottom", "pageHelperAbRight"].forEach((key) => {
if (cf[key] !== "unset") {
let pos = parseInt(cf[key]);
const screenLimit = key.endsWith("Right") || key.endsWith("Left") ? window.screen.width : window.screen.height;
if (isNaN(pos) || pos < 5 || pos > screenLimit) {
cf[key] = "5px";
changed = true;
}
}
});
if (!["pagination", "continuous"].includes(cf.readMode)) {
cf.readMode = "pagination";
changed = true;
}
if (changed) {
saveConf(cf);
}
return cf;
}
function saveConf(c) {
storage.setItem(CONFIG_KEY, JSON.stringify(c));
}
const conf = getConf();
const ConfigItems = [
{ key: "colCount", typ: "number" },
{ key: "threads", typ: "number" },
{ key: "downloadThreads", typ: "number" },
{ key: "paginationIMGCount", typ: "number" },
{ key: "timeout", typ: "number" },
{ key: "preventScrollPageTime", typ: "number" },
{ key: "autoPageInterval", typ: "number" },
{ key: "fetchOriginal", typ: "boolean", gridColumnRange: [1, 6] },
{ key: "autoLoad", typ: "boolean", gridColumnRange: [6, 11] },
{ key: "reversePages", typ: "boolean", gridColumnRange: [1, 6] },
{ key: "autoPlay", typ: "boolean", gridColumnRange: [6, 11] },
{ key: "autoLoadInBackground", typ: "boolean", gridColumnRange: [1, 6] },
{ key: "autoOpen", typ: "boolean", gridColumnRange: [6, 11] },
{ key: "disableCssAnimation", typ: "boolean", gridColumnRange: [1, 11] },
{ key: "autoCollapsePanel", typ: "boolean", gridColumnRange: [1, 11] },
{
key: "readMode",
typ: "select",
options: [
{ value: "pagination", display: "Pagination" },
{ value: "continuous", display: "Continuous" }
]
},
{
key: "stickyMouse",
typ: "select",
options: [
{ value: "enable", display: "Enable" },
{ value: "reverse", display: "Reverse" },
{ value: "disable", display: "Disable" }
]
},
{
key: "minifyPageHelper",
typ: "select",
options: [
{ value: "always", display: "Always" },
{ value: "inBigMode", display: "InBigMode" },
{ value: "never", display: "Never" }
]
},
{
key: "hitomiFormat",
typ: "select",
options: [
{ value: "auto", display: "Auto" },
{ value: "avif", display: "Avif" },
{ value: "webp", display: "Webp" },
{ value: "jxl", display: "Jxl" }
],
displayInSite: /hitomi.la\//
}
];
function evLog(level, msg, ...info) {
if (level === "debug" && !conf.debug)
return;
if (level === "error") {
console.warn((/* @__PURE__ */ new Date()).toLocaleString(), "EHVP:" + msg, ...info);
} else {
console.info((/* @__PURE__ */ new Date()).toLocaleString(), "EHVP:" + msg, ...info);
}
}
class EventManager {
events;
constructor() {
this.events = /* @__PURE__ */ new Map();
}
emit(id, ...args) {
if (!["imf-download-state-change"].includes(id)) {
evLog("debug", "event bus emitted: ", id);
}
const cbs = this.events.get(id);
if (cbs) {
cbs.forEach((cb) => cb(...args));
}
}
subscribe(id, cb) {
evLog("info", "event bus subscribed: ", id);
const cbs = this.events.get(id);
if (cbs) {
cbs.push(cb);
} else {
this.events.set(id, [cb]);
}
}
reset() {
this.events = /* @__PURE__ */ new Map();
}
}
const EBUS = new EventManager();
class Debouncer {
tids;
mode;
lastExecTime;
constructor(mode) {
this.tids = {};
this.lastExecTime = Date.now();
this.mode = mode || "debounce";
}
addEvent(id, event, timeout) {
if (this.mode === "throttle") {
const now = Date.now();
if (now - this.lastExecTime >= timeout) {
this.lastExecTime = now;
event();
}
} else if (this.mode === "debounce") {
window.clearTimeout(this.tids[id]);
this.tids[id] = window.setTimeout(event, timeout);
}
}
}
const HOST_REGEX = /\/\/([^\/]*)\//;
function xhrWapper(url, respType, cb, timeout) {
return _GM_xmlhttpRequest({
method: "GET",
url,
timeout: timeout || 0,
responseType: respType,
nocache: false,
revalidate: false,
// fetch: false,
headers: {
"Host": HOST_REGEX.exec(url)?.[1] || window.location.host,
// "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/106.0",
"Accept": "image/avif,image/webp,*/*",
// "Accept-Language": "en-US,en;q=0.5",
// "Accept-Encoding": "gzip, deflate, br",
// "Connection": "keep-alive",
"Referer": window.location.href,
// "Sec-Fetch-Dest": "image",
// "Sec-Fetch-Mode": "no-cors",
// "Sec-Fetch-Site": "cross-site",
"Cache-Control": "public, max-age=2592000, immutable"
},
...cb
}).abort;
}
function fetchImage(url) {
return new Promise((resolve, reject) => {
xhrWapper(url, "blob", {
onload: (response) => resolve(response.response),
onerror: (error) => reject(error)
}, 10 * 1e3);
});
}
var FetchState = /* @__PURE__ */ ((FetchState2) => {
FetchState2[FetchState2["FAILED"] = 0] = "FAILED";
FetchState2[FetchState2["URL"] = 1] = "URL";
FetchState2[FetchState2["DATA"] = 2] = "DATA";
FetchState2[FetchState2["DONE"] = 3] = "DONE";
return FetchState2;
})(FetchState || {});
class IMGFetcher {
node;
originURL;
stage = 1 /* URL */;
tryTimes = 0;
lock = false;
/// 0: not rendered, 1: rendered tumbinal, 2: rendered big image
rendered = 0;
data;
contentType;
blobUrl;
downloadState;
downloadBar;
timeoutId;
matcher;
chapterIndex;
constructor(root, matcher, chapterIndex) {
this.node = root;
this.node.onclick = () => EBUS.emit("imf-on-click", this);
this.downloadState = { total: 100, loaded: 0, readyState: 0 };
this.matcher = matcher;
this.chapterIndex = chapterIndex;
}
create() {
return this.node.create();
}
// 刷新下载状态
setDownloadState(newState) {
this.downloadState = { ...this.downloadState, ...newState };
this.node.progress(this.downloadState);
EBUS.emit("imf-download-state-change");
}
async start(index) {
if (this.lock)
return;
this.lock = true;
try {
this.node.changeStyle("fetching");
await this.fetchImage();
this.node.changeStyle("fetched");
EBUS.emit("imf-on-finished", index, true, this);
} catch (error) {
this.node.changeStyle("failed");
evLog("error", `IMG-FETCHER ERROR:`, error);
this.stage = 0 /* FAILED */;
EBUS.emit("imf-on-finished", index, false, this);
} finally {
this.lock = false;
}
}
retry() {
if (this.stage !== 3 /* DONE */) {
this.node.changeStyle();
this.stage = 1 /* URL */;
}
}
async fetchImage() {
this.tryTimes = 0;
while (this.tryTimes < 3) {
switch (this.stage) {
case 0 /* FAILED */:
case 1 /* URL */:
let meta = await this.fetchOriginMeta();
if (meta !== null) {
this.originURL = meta.url;
if (meta.title) {
this.node.title = meta.title;
if (this.node.imgElement) {
this.node.imgElement.title = meta.title;
}
}
this.stage = 2 /* DATA */;
} else {
this.tryTimes++;
}
break;
case 2 /* DATA */:
const ret = await this.fetchImageData();
if (ret !== null) {
[this.data, this.contentType] = ret;
[this.data, this.contentType] = await this.matcher.processData(this.data, this.contentType, this.originURL);
this.blobUrl = URL.createObjectURL(new Blob([this.data], { type: this.contentType }));
this.node.onloaded(this.blobUrl, this.contentType);
if (this.rendered === 2) {
this.node.render();
}
this.stage = 3 /* DONE */;
} else {
this.stage = 1 /* URL */;
this.tryTimes++;
}
break;
case 3 /* DONE */:
return;
}
}
throw new Error(`Fetch image failed, reach max try times, current stage: ${this.stage}`);
}
async fetchOriginMeta() {
try {
const meta = await this.matcher.fetchOriginMeta(this.node.href, this.tryTimes > 0, this.chapterIndex);
if (!meta) {
evLog("error", "Fetch URL failed, the URL is empty");
return null;
}
return meta;
} catch (error) {
evLog("error", `Fetch URL error:`, error);
return null;
}
}
async fetchImageData() {
try {
const data = await this.fetchBigImage();
if (data == null) {
throw new Error(`Data is null, image url:${this.originURL}`);
}
return data.arrayBuffer().then((buffer) => [new Uint8Array(buffer), data.type]);
} catch (error) {
evLog("error", `Fetch image data error:`, error);
return null;
}
}
render() {
switch (this.rendered) {
case 0:
case 1:
this.node.render();
this.rendered = 2;
if (this.stage === 3 /* DONE */)
this.node.changeStyle("fetched");
break;
}
}
unrender() {
if (this.rendered === 1 || this.rendered === 0)
return;
this.rendered = 1;
this.node.unrender();
}
async fetchBigImage() {
if (this.originURL?.startsWith("blob:")) {
return await fetch(this.originURL).then((resp) => resp.blob());
}
const imgFetcher = this;
return new Promise(async (resolve, reject) => {
const debouncer = new Debouncer();
let abort;
const timeout = () => {
debouncer.addEvent("XHR_TIMEOUT", () => {
reject("timeout");
abort();
}, conf.timeout * 1e3);
};
abort = xhrWapper(imgFetcher.originURL, "blob", {
onload: function(response) {
let data = response.response;
if (data.type === "text/html") {
console.error("warn: fetch big image data type is not blob: ", data);
}
try {
imgFetcher.setDownloadState({ readyState: response.readyState });
} catch (error) {
evLog("error", "warn: fetch big image data onload setDownloadState error:", error);
}
resolve(data);
},
onerror: function(response) {
reject(`error:${response.error}, response:${response.response}`);
},
onprogress: function(response) {
imgFetcher.setDownloadState({ total: response.total, loaded: response.loaded, readyState: response.readyState });
timeout();
},
onloadstart: function() {
imgFetcher.setDownloadState(imgFetcher.downloadState);
}
});
timeout();
});
}
}
const lang = navigator.language;
const i18nIndex = lang.startsWith("zh") ? 1 : 0;
class I18nValue extends Array {
constructor(...value) {
super(...value);
}
get() {
return this[i18nIndex];
}
}
const keyboardCustom = {
inMain: {
"open-full-view-grid": new I18nValue("Enter Read Mode", "进入阅读模式")
},
inBigImageMode: {
"step-image-prev": new I18nValue("Go Prev Image", "切换到上一张图片"),
"step-image-next": new I18nValue("Go Next Image", "切换到下一张图片"),
"exit-big-image-mode": new I18nValue("Exit Big Image Mode", "退出大图模式"),
"step-to-first-image": new I18nValue("Go First Image", "跳转到第一张图片"),
"step-to-last-image": new I18nValue("Go Last Image", "跳转到最后一张图片"),
"scale-image-increase": new I18nValue("Increase Image Scale", "放大图片"),
"scale-image-decrease": new I18nValue("Decrease Image Scale", "缩小图片"),
"scroll-image-up": new I18nValue("Scroll Image Up (Please Keep Default Keys)", "向上滚动图片 (请保留默认按键)"),
"scroll-image-down": new I18nValue("Scroll Image Down (Please Keep Default Keys)", "向下滚动图片 (请保留默认按键)")
},
inFullViewGrid: {
"open-big-image-mode": new I18nValue("Enter Big Image Mode", "进入大图阅读模式"),
"pause-auto-load-temporarily": new I18nValue("Pause Auto Load Temporarily", "临时停止自动加载"),
"exit-full-view-grid": new I18nValue("Exit Read Mode", "退出阅读模式"),
"columns-increase": new I18nValue("Increase Columns ", "增加每行数量"),
"columns-decrease": new I18nValue("Decrease Columns ", "减少每行数量"),
"back-chapters-selection": new I18nValue("Back to Chapters Selection", "返回章节选择")
}
};
const i18n = {
imageScale: new I18nValue("SCALE", "缩放"),
download: new I18nValue("DL", "下载"),
config: new I18nValue("CONF", "配置"),
backChapters: new I18nValue("Chapters", "章节"),
autoPagePlay: new I18nValue("PLAY", "播放"),
autoPagePause: new I18nValue("PAUSE", "暂停"),
autoPlay: new I18nValue("Auto Page", "自动翻页"),
autoPlayTooltip: new I18nValue("Auto Page when entering the big image readmode.", "当阅读大图时,开启自动播放模式。"),
preventScrollPageTime: new I18nValue("Flip Page Time", "滚动翻页时间"),
preventScrollPageTimeTooltip: new I18nValue("In Read Mode:Single Page, when scrolling through the content, prevent immediate page flipping when reaching the bottom, improve the reading experience. Set to 0 to disable this feature, measured in milliseconds.", "在单页阅读模式下,滚动浏览时,阻止滚动到底部时立即翻页,提升阅读体验。设置为0时则为禁用此功能,单位为毫秒。"),
collapse: new I18nValue("FOLD", "收起"),
colCount: new I18nValue("Columns", "每行数量"),
readMode: new I18nValue("Read Mode", "阅读模式"),
autoPageInterval: new I18nValue("Auto Page Interval", "自动翻页间隔"),
autoPageIntervalTooltip: new I18nValue("Use the mouse wheel on Input box to adjust the interval time.", "在输入框上使用鼠标滚轮快速修改间隔时间"),
readModeTooltip: new I18nValue("Switch to the next picture when scrolling, otherwise read continuously", "滚动时切换到下一张图片,否则连续阅读"),
threads: new I18nValue("PreloadThreads", "最大同时加载"),
threadsTooltip: new I18nValue("Max Preload Threads", "大图浏览时,每次滚动到下一张时,预加载的图片数量,大于1时体现为越看加载的图片越多,将提升浏览体验。"),
downloadThreads: new I18nValue("DownloadThreads", "最大同时下载"),
downloadThreadsTooltip: new I18nValue("Max Download Threads, suggest: <5", "下载模式下,同时加载的图片数量,建议小于等于5"),
timeout: new I18nValue("Timeout(second)", "超时时间(秒)"),
fetchOriginal: new I18nValue("Raw Image", "最佳质量"),
autoLoad: new I18nValue("Auto Load", "自动加载"),
autoLoadTooltip: new I18nValue("", "进入本脚本的浏览模式后,即使不浏览也会一张接一张的加载图片。直至所有图片加载完毕。"),
fetchOriginalTooltip: new I18nValue("enable will download the original source, cost more traffic and quotas", "启用后,将加载未经过压缩的原档文件,下载打包后的体积也与画廊所标体积一致。
注意:这将消耗更多的流量与配额,请酌情启用。"),
forceDownload: new I18nValue("Take Loaded", "强制下载已加载的"),
downloadStart: new I18nValue("Start Download", "开始下载"),
downloading: new I18nValue("Downloading...", "下载中..."),
downloadFailed: new I18nValue("Failed(Retry)", "下载失败(重试)"),
downloaded: new I18nValue("Downloaded", "下载完成"),
packaging: new I18nValue("Packaging...", "打包中..."),
reversePages: new I18nValue("Reverse Pages", "反向翻页"),
reversePagesTooltip: new I18nValue("Clicking on the side navigation, if enable then reverse paging, which is a reading style similar to Japanese manga where pages are read from right to left.", "点击侧边导航时,是否反向翻页,反向翻页类似日本漫画那样的从右到左的阅读方式。"),
autoCollapsePanel: new I18nValue("Auto Fold Control Panel", "自动收起控制面板"),
autoCollapsePanelTooltip: new I18nValue("When the mouse is moved out of the control panel, the control panel will automatically fold. If disabled, the display of the control panel can only be toggled through the button on the control bar.", "当鼠标移出控制面板时,自动收起控制面板。禁用此选项后,只能通过控制栏上的按钮切换控制面板的显示。"),
disableCssAnimation: new I18nValue("Disable Animation", "禁用动画"),
disableCssAnimationTooltip: new I18nValue("Valid after refreshing the page", "刷新页面后生效"),
stickyMouse: new I18nValue("Sticky Mouse", "黏糊糊鼠标"),
stickyMouseTooltip: new I18nValue("In non-continuous reading mode, scroll a single image automatically by moving the mouse.", "非连续阅读模式下,通过鼠标移动来自动滚动单张图片。"),
minifyPageHelper: new I18nValue("Minify Control Bar", "最小化控制栏"),
minifyPageHelperTooltip: new I18nValue("Minify Control Bar", "最小化控制栏"),
paginationIMGCount: new I18nValue("Images Per Page", "每页图片数量"),
paginationIMGCountTooltip: new I18nValue("In Pagination Read mode, the number of images displayed on each page", "在翻页阅读模式下,每页展示的图片数量"),
hitomiFormat: new I18nValue("Hitomi Image Format", "Hitomi 图片格式"),
hitomiFormatTooltip: new I18nValue("In Hitomi, Fetch images by the format.
if Auto then try Avif > Jxl > Webp, Requires Refresh", "在Hitomi中的源图格式。
如果是Auto,则优先获取Avif > Jxl > Webp,修改后需要刷新生效。"),
autoOpen: new I18nValue("Auto Open", "自动展开"),
autoOpenTooltip: new I18nValue("Automatically open after the gallery page is loaded", "进入画廊页面后,自动展开阅读视图。"),
autoLoadInBackground: new I18nValue("Keep Loading", "后台加载"),
autoLoadInBackgroundTooltip: new I18nValue("Keep Auto-Loading after the tab loses focus", "当标签页失去焦点后保持自动加载。"),
dragToMove: new I18nValue("Drag to Move", "拖动移动"),
originalCheck: new I18nValue("Enable RawImage Transient", "未启用最佳质量图片,点击此处临时开启最佳质量"),
showHelp: new I18nValue("Help", "帮助"),
showKeyboard: new I18nValue("Keyboard", "快捷键"),
showExcludes: new I18nValue("Excludes", "站点排除"),
letUsStar: new I18nValue("Let's Star", "点星"),
help: new I18nValue(`
Scale Image | mouse right + wheel or -/= |
Open Image(In thumbnails) | Enter |
Exit Image(In big mode) | Enter/Esc |
Open Specific Page(In thumbnails) | Input number(no echo) + Enter |
Switch Page | →/← |
Scroll Image | ↑/↓/Space |
Toggle Auto Load | p |
图片缩放 | 鼠标右键+滚轮 或 -/= |
打开大图(缩略图模式下) | 回车 |
退出大图(大图模式下) | 回车/Esc |
打开指定图片(缩略图模式下) | 直接输入数字(不回显) + 回车 |
切换图片 | →/← |
滚动图片 | ↑/↓ |
开关自动加载 | p |
* These objects are the currency accepted by SJCL's crypto functions. *
* ** Most of our crypto primitives operate on arrays of 4-byte words internally, * but many of them can take arguments that are not a multiple of 4 bytes. * This library encodes arrays of bits (whose size need not be a multiple of 8 * bits) as arrays of 32-bit words. The bits are packed, big-endian, into an * array of words, 32 bits at a time. Since the words are double-precision * floating point numbers, they fit some extra data. We use this (in a private, * possibly-changing manner) to encode the number of bits actually present * in the last word of the array. *
* ** Because bitwise ops clear this out-of-band data, these arrays can be passed * to ciphers like AES which want arrays of words. *
*/ const bitArray = { /** * Concatenate two bit arrays. * @param {bitArray} a1 The first array. * @param {bitArray} a2 The second array. * @return {bitArray} The concatenation of a1 and a2. */ concat(a1, a2) { if (a1.length === 0 || a2.length === 0) { return a1.concat(a2); } const last = a1[a1.length - 1], shift = bitArray.getPartial(last); if (shift === 32) { return a1.concat(a2); } else { return bitArray._shiftRight(a2, shift, last | 0, a1.slice(0, a1.length - 1)); } }, /** * Find the length of an array of bits. * @param {bitArray} a The array. * @return {Number} The length of a, in bits. */ bitLength(a) { const l = a.length; if (l === 0) { return 0; } const x = a[l - 1]; return (l - 1) * 32 + bitArray.getPartial(x); }, /** * Truncate an array. * @param {bitArray} a The array. * @param {Number} len The length to truncate to, in bits. * @return {bitArray} A new array, truncated to len bits. */ clamp(a, len) { if (a.length * 32 < len) { return a; } a = a.slice(0, Math.ceil(len / 32)); const l = a.length; len = len & 31; if (l > 0 && len) { a[l - 1] = bitArray.partial(len, a[l - 1] & 0x80000000 >> (len - 1), 1); } return a; }, /** * Make a partial word for a bit array. * @param {Number} len The number of bits in the word. * @param {Number} x The bits. * @param {Number} [_end=0] Pass 1 if x has already been shifted to the high side. * @return {Number} The partial word. */ partial(len, x, _end) { if (len === 32) { return x; } return (_end ? x | 0 : x << (32 - len)) + len * 0x10000000000; }, /** * Get the number of bits used by a partial word. * @param {Number} x The partial word. * @return {Number} The number of bits used by the partial word. */ getPartial(x) { return Math.round(x / 0x10000000000) || 32; }, /** Shift an array right. * @param {bitArray} a The array to shift. * @param {Number} shift The number of bits to shift. * @param {Number} [carry=0] A byte to carry in * @param {bitArray} [out=[]] An array to prepend to the output. * @private */ _shiftRight(a, shift, carry, out) { if (out === undefined) { out = []; } for (; shift >= 32; shift -= 32) { out.push(carry); carry = 0; } if (shift === 0) { return out.concat(a); } for (let i = 0; i < a.length; i++) { out.push(carry | a[i] >>> shift); carry = a[i] << (32 - shift); } const last2 = a.length ? a[a.length - 1] : 0; const shift2 = bitArray.getPartial(last2); out.push(bitArray.partial(shift + shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(), 1)); return out; } }; /** @fileOverview Bit array codec implementations. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** * Arrays of bytes * @namespace */ const codec = { bytes: { /** Convert from a bitArray to an array of bytes. */ fromBits(arr) { const bl = bitArray.bitLength(arr); const byteLength = bl / 8; const out = new Uint8Array(byteLength); let tmp; for (let i = 0; i < byteLength; i++) { if ((i & 3) === 0) { tmp = arr[i / 4]; } out[i] = tmp >>> 24; tmp <<= 8; } return out; }, /** Convert from an array of bytes to a bitArray. */ toBits(bytes) { const out = []; let i; let tmp = 0; for (i = 0; i < bytes.length; i++) { tmp = tmp << 8 | bytes[i]; if ((i & 3) === 3) { out.push(tmp); tmp = 0; } } if (i & 3) { out.push(bitArray.partial(8 * (i & 3), tmp)); } return out; } } }; const hash = {}; /** * Context for a SHA-1 operation in progress. * @constructor */ hash.sha1 = class { constructor(hash) { const sha1 = this; /** * The hash's block size, in bits. * @constant */ sha1.blockSize = 512; /** * The SHA-1 initialization vector. * @private */ sha1._init = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]; /** * The SHA-1 hash key. * @private */ sha1._key = [0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6]; if (hash) { sha1._h = hash._h.slice(0); sha1._buffer = hash._buffer.slice(0); sha1._length = hash._length; } else { sha1.reset(); } } /** * Reset the hash state. * @return this */ reset() { const sha1 = this; sha1._h = sha1._init.slice(0); sha1._buffer = []; sha1._length = 0; return sha1; } /** * Input several words to the hash. * @param {bitArray|String} data the data to hash. * @return this */ update(data) { const sha1 = this; if (typeof data === "string") { data = codec.utf8String.toBits(data); } const b = sha1._buffer = bitArray.concat(sha1._buffer, data); const ol = sha1._length; const nl = sha1._length = ol + bitArray.bitLength(data); if (nl > 9007199254740991) { throw new Error("Cannot hash more than 2^53 - 1 bits"); } const c = new Uint32Array(b); let j = 0; for (let i = sha1.blockSize + ol - ((sha1.blockSize + ol) & (sha1.blockSize - 1)); i <= nl; i += sha1.blockSize) { sha1._block(c.subarray(16 * j, 16 * (j + 1))); j += 1; } b.splice(0, 16 * j); return sha1; } /** * Complete hashing and output the hash value. * @return {bitArray} The hash value, an array of 5 big-endian words. TODO */ finalize() { const sha1 = this; let b = sha1._buffer; const h = sha1._h; // Round out and push the buffer b = bitArray.concat(b, [bitArray.partial(1, 1)]); // Round out the buffer to a multiple of 16 words, less the 2 length words. for (let i = b.length + 2; i & 15; i++) { b.push(0); } // append the length b.push(Math.floor(sha1._length / 0x100000000)); b.push(sha1._length | 0); while (b.length) { sha1._block(b.splice(0, 16)); } sha1.reset(); return h; } /** * The SHA-1 logical functions f(0), f(1), ..., f(79). * @private */ _f(t, b, c, d) { if (t <= 19) { return (b & c) | (~b & d); } else if (t <= 39) { return b ^ c ^ d; } else if (t <= 59) { return (b & c) | (b & d) | (c & d); } else if (t <= 79) { return b ^ c ^ d; } } /** * Circular left-shift operator. * @private */ _S(n, x) { return (x << n) | (x >>> 32 - n); } /** * Perform one cycle of SHA-1. * @param {Uint32Array|bitArray} words one block of words. * @private */ _block(words) { const sha1 = this; const h = sha1._h; // When words is passed to _block, it has 16 elements. SHA1 _block // function extends words with new elements (at the end there are 80 elements). // The problem is that if we use Uint32Array instead of Array, // the length of Uint32Array cannot be changed. Thus, we replace words with a // normal Array here. const w = Array(80); // do not use Uint32Array here as the instantiation is slower for (let j = 0; j < 16; j++) { w[j] = words[j]; } let a = h[0]; let b = h[1]; let c = h[2]; let d = h[3]; let e = h[4]; for (let t = 0; t <= 79; t++) { if (t >= 16) { w[t] = sha1._S(1, w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]); } const tmp = (sha1._S(5, a) + sha1._f(t, b, c, d) + e + w[t] + sha1._key[Math.floor(t / 20)]) | 0; e = d; d = c; c = sha1._S(30, b); b = a; a = tmp; } h[0] = (h[0] + a) | 0; h[1] = (h[1] + b) | 0; h[2] = (h[2] + c) | 0; h[3] = (h[3] + d) | 0; h[4] = (h[4] + e) | 0; } }; /** @fileOverview Low-level AES implementation. * * This file contains a low-level implementation of AES, optimized for * size and for efficiency on several browsers. It is based on * OpenSSL's aes_core.c, a public-domain implementation by Vincent * Rijmen, Antoon Bosselaers and Paulo Barreto. * * An older version of this implementation is available in the public * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh, * Stanford University 2008-2010 and BSD-licensed for liability * reasons. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ const cipher = {}; /** * Schedule out an AES key for both encryption and decryption. This * is a low-level class. Use a cipher mode to do bulk encryption. * * @constructor * @param {Array} key The key as an array of 4, 6 or 8 words. */ cipher.aes = class { constructor(key) { /** * The expanded S-box and inverse S-box tables. These will be computed * on the client so that we don't have to send them down the wire. * * There are two tables, _tables[0] is for encryption and * _tables[1] is for decryption. * * The first 4 sub-tables are the expanded S-box with MixColumns. The * last (_tables[01][4]) is the S-box itself. * * @private */ const aes = this; aes._tables = [[[], [], [], [], []], [[], [], [], [], []]]; if (!aes._tables[0][0][0]) { aes._precompute(); } const sbox = aes._tables[0][4]; const decTable = aes._tables[1]; const keyLen = key.length; let i, encKey, decKey, rcon = 1; if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { throw new Error("invalid aes key size"); } aes._key = [encKey = key.slice(0), decKey = []]; // schedule encryption keys for (i = keyLen; i < 4 * keyLen + 28; i++) { let tmp = encKey[i - 1]; // apply sbox if (i % keyLen === 0 || (keyLen === 8 && i % keyLen === 4)) { tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon if (i % keyLen === 0) { tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; rcon = rcon << 1 ^ (rcon >> 7) * 283; } } encKey[i] = encKey[i - keyLen] ^ tmp; } // schedule decryption keys for (let j = 0; i; j++, i--) { const tmp = encKey[j & 3 ? i : i - 4]; if (i <= 4 || j < 4) { decKey[j] = tmp; } else { decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; } } } // public /* Something like this might appear here eventually name: "AES", blockSize: 4, keySizes: [4,6,8], */ /** * Encrypt an array of 4 big-endian words. * @param {Array} data The plaintext. * @return {Array} The ciphertext. */ encrypt(data) { return this._crypt(data, 0); } /** * Decrypt an array of 4 big-endian words. * @param {Array} data The ciphertext. * @return {Array} The plaintext. */ decrypt(data) { return this._crypt(data, 1); } /** * Expand the S-box tables. * * @private */ _precompute() { const encTable = this._tables[0]; const decTable = this._tables[1]; const sbox = encTable[4]; const sboxInv = decTable[4]; const d = []; const th = []; let xInv, x2, x4, x8; // Compute double and third tables for (let i = 0; i < 256; i++) { th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; } for (let x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { // Compute sbox let s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; s = s >> 8 ^ s & 255 ^ 99; sbox[x] = s; sboxInv[s] = x; // Compute MixColumns x8 = d[x4 = d[x2 = d[x]]]; let tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; let tEnc = d[s] * 0x101 ^ s * 0x1010100; for (let i = 0; i < 4; i++) { encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; } } // Compactify. Considerable speedup on Firefox. for (let i = 0; i < 5; i++) { encTable[i] = encTable[i].slice(0); decTable[i] = decTable[i].slice(0); } } /** * Encryption and decryption core. * @param {Array} input Four words to be encrypted or decrypted. * @param dir The direction, 0 for encrypt and 1 for decrypt. * @return {Array} The four encrypted or decrypted words. * @private */ _crypt(input, dir) { if (input.length !== 4) { throw new Error("invalid aes block size"); } const key = this._key[dir]; const nInnerRounds = key.length / 4 - 2; const out = [0, 0, 0, 0]; const table = this._tables[dir]; // load up the tables const t0 = table[0]; const t1 = table[1]; const t2 = table[2]; const t3 = table[3]; const sbox = table[4]; // state variables a,b,c,d are loaded with pre-whitened data let a = input[0] ^ key[0]; let b = input[dir ? 3 : 1] ^ key[1]; let c = input[2] ^ key[2]; let d = input[dir ? 1 : 3] ^ key[3]; let kIndex = 4; let a2, b2, c2; // Inner rounds. Cribbed from OpenSSL. for (let i = 0; i < nInnerRounds; i++) { a2 = t0[a >>> 24] ^ t1[b >> 16 & 255] ^ t2[c >> 8 & 255] ^ t3[d & 255] ^ key[kIndex]; b2 = t0[b >>> 24] ^ t1[c >> 16 & 255] ^ t2[d >> 8 & 255] ^ t3[a & 255] ^ key[kIndex + 1]; c2 = t0[c >>> 24] ^ t1[d >> 16 & 255] ^ t2[a >> 8 & 255] ^ t3[b & 255] ^ key[kIndex + 2]; d = t0[d >>> 24] ^ t1[a >> 16 & 255] ^ t2[b >> 8 & 255] ^ t3[c & 255] ^ key[kIndex + 3]; kIndex += 4; a = a2; b = b2; c = c2; } // Last round. for (let i = 0; i < 4; i++) { out[dir ? 3 & -i : i] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; a2 = a; a = b; b = c; c = d; d = a2; } return out; } }; /** * Random values * @namespace */ const random = { /** * Generate random words with pure js, cryptographically not as strong & safe as native implementation. * @param {TypedArray} typedArray The array to fill. * @return {TypedArray} The random values. */ getRandomValues(typedArray) { const words = new Uint32Array(typedArray.buffer); const r = (m_w) => { let m_z = 0x3ade68b1; const mask = 0xffffffff; return function () { m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask; m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask; const result = ((((m_z << 0x10) + m_w) & mask) / 0x100000000) + .5; return result * (Math.random() > .5 ? 1 : -1); }; }; for (let i = 0, rcache; i < typedArray.length; i += 4) { const _r = r((rcache || Math.random()) * 0x100000000); rcache = _r() * 0x3ade67b7; words[i / 4] = (_r() * 0x100000000) | 0; } return typedArray; } }; /** @fileOverview CTR mode implementation. * * Special thanks to Roy Nicholson for pointing out a bug in our * implementation. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** Brian Gladman's CTR Mode. * @constructor * @param {Object} _prf The aes instance to generate key. * @param {bitArray} _iv The iv for ctr mode, it must be 128 bits. */ const mode = {}; /** * Brian Gladman's CTR Mode. * @namespace */ mode.ctrGladman = class { constructor(prf, iv) { this._prf = prf; this._initIv = iv; this._iv = iv; } reset() { this._iv = this._initIv; } /** Input some data to calculate. * @param {bitArray} data the data to process, it must be intergral multiple of 128 bits unless it's the last. */ update(data) { return this.calculate(this._prf, data, this._iv); } incWord(word) { if (((word >> 24) & 0xff) === 0xff) { //overflow let b1 = (word >> 16) & 0xff; let b2 = (word >> 8) & 0xff; let b3 = word & 0xff; if (b1 === 0xff) { // overflow b1 b1 = 0; if (b2 === 0xff) { b2 = 0; if (b3 === 0xff) { b3 = 0; } else { ++b3; } } else { ++b2; } } else { ++b1; } word = 0; word += (b1 << 16); word += (b2 << 8); word += b3; } else { word += (0x01 << 24); } return word; } incCounter(counter) { if ((counter[0] = this.incWord(counter[0])) === 0) { // encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8 counter[1] = this.incWord(counter[1]); } } calculate(prf, data, iv) { let l; if (!(l = data.length)) { return []; } const bl = bitArray.bitLength(data); for (let i = 0; i < l; i += 4) { this.incCounter(iv); const e = prf.encrypt(iv); data[i] ^= e[0]; data[i + 1] ^= e[1]; data[i + 2] ^= e[2]; data[i + 3] ^= e[3]; } return bitArray.clamp(data, bl); } }; const misc = { importKey(password) { return new misc.hmacSha1(codec.bytes.toBits(password)); }, pbkdf2(prf, salt, count, length) { count = count || 10000; if (length < 0 || count < 0) { throw new Error("invalid params to pbkdf2"); } const byteLength = ((length >> 5) + 1) << 2; let u, ui, i, j, k; const arrayBuffer = new ArrayBuffer(byteLength); const out = new DataView(arrayBuffer); let outLength = 0; const b = bitArray; salt = codec.bytes.toBits(salt); for (k = 1; outLength < (byteLength || 1); k++) { u = ui = prf.encrypt(b.concat(salt, [k])); for (i = 1; i < count; i++) { ui = prf.encrypt(ui); for (j = 0; j < ui.length; j++) { u[j] ^= ui[j]; } } for (i = 0; outLength < (byteLength || 1) && i < u.length; i++) { out.setInt32(outLength, u[i]); outLength += 4; } } return arrayBuffer.slice(0, length / 8); } }; /** @fileOverview HMAC implementation. * * @author Emily Stark * @author Mike Hamburg * @author Dan Boneh */ /** HMAC with the specified hash function. * @constructor * @param {bitArray} key the key for HMAC. * @param {Object} [Hash=hash.sha1] The hash function to use. */ misc.hmacSha1 = class { constructor(key) { const hmac = this; const Hash = hmac._hash = hash.sha1; const exKey = [[], []]; hmac._baseHash = [new Hash(), new Hash()]; const bs = hmac._baseHash[0].blockSize / 32; if (key.length > bs) { key = new Hash().update(key).finalize(); } for (let i = 0; i < bs; i++) { exKey[0][i] = key[i] ^ 0x36363636; exKey[1][i] = key[i] ^ 0x5C5C5C5C; } hmac._baseHash[0].update(exKey[0]); hmac._baseHash[1].update(exKey[1]); hmac._resultHash = new Hash(hmac._baseHash[0]); } reset() { const hmac = this; hmac._resultHash = new hmac._hash(hmac._baseHash[0]); hmac._updated = false; } update(data) { const hmac = this; hmac._updated = true; hmac._resultHash.update(data); } digest() { const hmac = this; const w = hmac._resultHash.finalize(); const result = new (hmac._hash)(hmac._baseHash[1]).update(w).finalize(); hmac.reset(); return result; } encrypt(data) { if (!this._updated) { this.update(data); return this.digest(data); } else { throw new Error("encrypt on already updated hmac called!"); } } }; /* Copyright (c) 2022 Gildas Lormeau. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of the authors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ const GET_RANDOM_VALUES_SUPPORTED = typeof crypto != UNDEFINED_TYPE && typeof crypto.getRandomValues == FUNCTION_TYPE; const ERR_INVALID_PASSWORD = "Invalid password"; const ERR_INVALID_SIGNATURE = "Invalid signature"; const ERR_ABORT_CHECK_PASSWORD = "zipjs-abort-check-password"; function getRandomValues(array) { if (GET_RANDOM_VALUES_SUPPORTED) { return crypto.getRandomValues(array); } else { return random.getRandomValues(array); } } /* Copyright (c) 2022 Gildas Lormeau. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of the authors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ const BLOCK_LENGTH = 16; const RAW_FORMAT = "raw"; const PBKDF2_ALGORITHM = { name: "PBKDF2" }; const HASH_ALGORITHM = { name: "HMAC" }; const HASH_FUNCTION = "SHA-1"; const BASE_KEY_ALGORITHM = Object.assign({ hash: HASH_ALGORITHM }, PBKDF2_ALGORITHM); const DERIVED_BITS_ALGORITHM = Object.assign({ iterations: 1000, hash: { name: HASH_FUNCTION } }, PBKDF2_ALGORITHM); const DERIVED_BITS_USAGE = ["deriveBits"]; const SALT_LENGTH = [8, 12, 16]; const KEY_LENGTH = [16, 24, 32]; const SIGNATURE_LENGTH = 10; const COUNTER_DEFAULT_VALUE = [0, 0, 0, 0]; // deno-lint-ignore valid-typeof const CRYPTO_API_SUPPORTED = typeof crypto != UNDEFINED_TYPE; const subtle = CRYPTO_API_SUPPORTED && crypto.subtle; const SUBTLE_API_SUPPORTED = CRYPTO_API_SUPPORTED && typeof subtle != UNDEFINED_TYPE; const codecBytes = codec.bytes; const Aes = cipher.aes; const CtrGladman = mode.ctrGladman; const HmacSha1 = misc.hmacSha1; let IMPORT_KEY_SUPPORTED = CRYPTO_API_SUPPORTED && SUBTLE_API_SUPPORTED && typeof subtle.importKey == FUNCTION_TYPE; let DERIVE_BITS_SUPPORTED = CRYPTO_API_SUPPORTED && SUBTLE_API_SUPPORTED && typeof subtle.deriveBits == FUNCTION_TYPE; class AESDecryptionStream extends TransformStream { constructor({ password, rawPassword, signed, encryptionStrength, checkPasswordOnly }) { super({ start() { Object.assign(this, { ready: new Promise(resolve => this.resolveReady = resolve), password: encodePassword(password, rawPassword), signed, strength: encryptionStrength - 1, pending: new Uint8Array() }); }, async transform(chunk, controller) { const aesCrypto = this; const { password, strength, resolveReady, ready } = aesCrypto; if (password) { await createDecryptionKeys(aesCrypto, strength, password, subarray(chunk, 0, SALT_LENGTH[strength] + 2)); chunk = subarray(chunk, SALT_LENGTH[strength] + 2); if (checkPasswordOnly) { controller.error(new Error(ERR_ABORT_CHECK_PASSWORD)); } else { resolveReady(); } } else { await ready; } const output = new Uint8Array(chunk.length - SIGNATURE_LENGTH - ((chunk.length - SIGNATURE_LENGTH) % BLOCK_LENGTH)); controller.enqueue(append(aesCrypto, chunk, output, 0, SIGNATURE_LENGTH, true)); }, async flush(controller) { const { signed, ctr, hmac, pending, ready } = this; if (hmac && ctr) { await ready; const chunkToDecrypt = subarray(pending, 0, pending.length - SIGNATURE_LENGTH); const originalSignature = subarray(pending, pending.length - SIGNATURE_LENGTH); let decryptedChunkArray = new Uint8Array(); if (chunkToDecrypt.length) { const encryptedChunk = toBits(codecBytes, chunkToDecrypt); hmac.update(encryptedChunk); const decryptedChunk = ctr.update(encryptedChunk); decryptedChunkArray = fromBits(codecBytes, decryptedChunk); } if (signed) { const signature = subarray(fromBits(codecBytes, hmac.digest()), 0, SIGNATURE_LENGTH); for (let indexSignature = 0; indexSignature < SIGNATURE_LENGTH; indexSignature++) { if (signature[indexSignature] != originalSignature[indexSignature]) { throw new Error(ERR_INVALID_SIGNATURE); } } } controller.enqueue(decryptedChunkArray); } } }); } } class AESEncryptionStream extends TransformStream { constructor({ password, rawPassword, encryptionStrength }) { // deno-lint-ignore prefer-const let stream; super({ start() { Object.assign(this, { ready: new Promise(resolve => this.resolveReady = resolve), password: encodePassword(password, rawPassword), strength: encryptionStrength - 1, pending: new Uint8Array() }); }, async transform(chunk, controller) { const aesCrypto = this; const { password, strength, resolveReady, ready } = aesCrypto; let preamble = new Uint8Array(); if (password) { preamble = await createEncryptionKeys(aesCrypto, strength, password); resolveReady(); } else { await ready; } const output = new Uint8Array(preamble.length + chunk.length - (chunk.length % BLOCK_LENGTH)); output.set(preamble, 0); controller.enqueue(append(aesCrypto, chunk, output, preamble.length, 0)); }, async flush(controller) { const { ctr, hmac, pending, ready } = this; if (hmac && ctr) { await ready; let encryptedChunkArray = new Uint8Array(); if (pending.length) { const encryptedChunk = ctr.update(toBits(codecBytes, pending)); hmac.update(encryptedChunk); encryptedChunkArray = fromBits(codecBytes, encryptedChunk); } stream.signature = fromBits(codecBytes, hmac.digest()).slice(0, SIGNATURE_LENGTH); controller.enqueue(concat(encryptedChunkArray, stream.signature)); } } }); stream = this; } } function append(aesCrypto, input, output, paddingStart, paddingEnd, verifySignature) { const { ctr, hmac, pending } = aesCrypto; const inputLength = input.length - paddingEnd; if (pending.length) { input = concat(pending, input); output = expand(output, inputLength - (inputLength % BLOCK_LENGTH)); } let offset; for (offset = 0; offset <= inputLength - BLOCK_LENGTH; offset += BLOCK_LENGTH) { const inputChunk = toBits(codecBytes, subarray(input, offset, offset + BLOCK_LENGTH)); if (verifySignature) { hmac.update(inputChunk); } const outputChunk = ctr.update(inputChunk); if (!verifySignature) { hmac.update(outputChunk); } output.set(fromBits(codecBytes, outputChunk), offset + paddingStart); } aesCrypto.pending = subarray(input, offset); return output; } async function createDecryptionKeys(decrypt, strength, password, preamble) { const passwordVerificationKey = await createKeys$1(decrypt, strength, password, subarray(preamble, 0, SALT_LENGTH[strength])); const passwordVerification = subarray(preamble, SALT_LENGTH[strength]); if (passwordVerificationKey[0] != passwordVerification[0] || passwordVerificationKey[1] != passwordVerification[1]) { throw new Error(ERR_INVALID_PASSWORD); } } async function createEncryptionKeys(encrypt, strength, password) { const salt = getRandomValues(new Uint8Array(SALT_LENGTH[strength])); const passwordVerification = await createKeys$1(encrypt, strength, password, salt); return concat(salt, passwordVerification); } async function createKeys$1(aesCrypto, strength, password, salt) { aesCrypto.password = null; const baseKey = await importKey(RAW_FORMAT, password, BASE_KEY_ALGORITHM, false, DERIVED_BITS_USAGE); const derivedBits = await deriveBits(Object.assign({ salt }, DERIVED_BITS_ALGORITHM), baseKey, 8 * ((KEY_LENGTH[strength] * 2) + 2)); const compositeKey = new Uint8Array(derivedBits); const key = toBits(codecBytes, subarray(compositeKey, 0, KEY_LENGTH[strength])); const authentication = toBits(codecBytes, subarray(compositeKey, KEY_LENGTH[strength], KEY_LENGTH[strength] * 2)); const passwordVerification = subarray(compositeKey, KEY_LENGTH[strength] * 2); Object.assign(aesCrypto, { keys: { key, authentication, passwordVerification }, ctr: new CtrGladman(new Aes(key), Array.from(COUNTER_DEFAULT_VALUE)), hmac: new HmacSha1(authentication) }); return passwordVerification; } async function importKey(format, password, algorithm, extractable, keyUsages) { if (IMPORT_KEY_SUPPORTED) { try { return await subtle.importKey(format, password, algorithm, extractable, keyUsages); } catch (_error) { IMPORT_KEY_SUPPORTED = false; return misc.importKey(password); } } else { return misc.importKey(password); } } async function deriveBits(algorithm, baseKey, length) { if (DERIVE_BITS_SUPPORTED) { try { return await subtle.deriveBits(algorithm, baseKey, length); } catch (_error) { DERIVE_BITS_SUPPORTED = false; return misc.pbkdf2(baseKey, algorithm.salt, DERIVED_BITS_ALGORITHM.iterations, length); } } else { return misc.pbkdf2(baseKey, algorithm.salt, DERIVED_BITS_ALGORITHM.iterations, length); } } function encodePassword(password, rawPassword) { if (rawPassword === UNDEFINED_VALUE) { return encodeText(password); } else { return rawPassword; } } function concat(leftArray, rightArray) { let array = leftArray; if (leftArray.length + rightArray.length) { array = new Uint8Array(leftArray.length + rightArray.length); array.set(leftArray, 0); array.set(rightArray, leftArray.length); } return array; } function expand(inputArray, length) { if (length && length > inputArray.length) { const array = inputArray; inputArray = new Uint8Array(length); inputArray.set(array, 0); } return inputArray; } function subarray(array, begin, end) { return array.subarray(begin, end); } function fromBits(codecBytes, chunk) { return codecBytes.fromBits(chunk); } function toBits(codecBytes, chunk) { return codecBytes.toBits(chunk); } /* Copyright (c) 2022 Gildas Lormeau. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of the authors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ const HEADER_LENGTH = 12; class ZipCryptoDecryptionStream extends TransformStream { constructor({ password, passwordVerification, checkPasswordOnly }) { super({ start() { Object.assign(this, { password, passwordVerification }); createKeys(this, password); }, transform(chunk, controller) { const zipCrypto = this; if (zipCrypto.password) { const decryptedHeader = decrypt(zipCrypto, chunk.subarray(0, HEADER_LENGTH)); zipCrypto.password = null; if (decryptedHeader[HEADER_LENGTH - 1] != zipCrypto.passwordVerification) { throw new Error(ERR_INVALID_PASSWORD); } chunk = chunk.subarray(HEADER_LENGTH); } if (checkPasswordOnly) { controller.error(new Error(ERR_ABORT_CHECK_PASSWORD)); } else { controller.enqueue(decrypt(zipCrypto, chunk)); } } }); } } class ZipCryptoEncryptionStream extends TransformStream { constructor({ password, passwordVerification }) { super({ start() { Object.assign(this, { password, passwordVerification }); createKeys(this, password); }, transform(chunk, controller) { const zipCrypto = this; let output; let offset; if (zipCrypto.password) { zipCrypto.password = null; const header = getRandomValues(new Uint8Array(HEADER_LENGTH)); header[HEADER_LENGTH - 1] = zipCrypto.passwordVerification; output = new Uint8Array(chunk.length + header.length); output.set(encrypt(zipCrypto, header), 0); offset = HEADER_LENGTH; } else { output = new Uint8Array(chunk.length); offset = 0; } output.set(encrypt(zipCrypto, chunk), offset); controller.enqueue(output); } }); } } function decrypt(target, input) { const output = new Uint8Array(input.length); for (let index = 0; index < input.length; index++) { output[index] = getByte(target) ^ input[index]; updateKeys(target, output[index]); } return output; } function encrypt(target, input) { const output = new Uint8Array(input.length); for (let index = 0; index < input.length; index++) { output[index] = getByte(target) ^ input[index]; updateKeys(target, input[index]); } return output; } function createKeys(target, password) { const keys = [0x12345678, 0x23456789, 0x34567890]; Object.assign(target, { keys, crcKey0: new Crc32(keys[0]), crcKey2: new Crc32(keys[2]), }); for (let index = 0; index < password.length; index++) { updateKeys(target, password.charCodeAt(index)); } } function updateKeys(target, byte) { let [key0, key1, key2] = target.keys; target.crcKey0.append([byte]); key0 = ~target.crcKey0.get(); key1 = getInt32(Math.imul(getInt32(key1 + getInt8(key0)), 134775813) + 1); target.crcKey2.append([key1 >>> 24]); key2 = ~target.crcKey2.get(); target.keys = [key0, key1, key2]; } function getByte(target) { const temp = target.keys[2] | 2; return getInt8(Math.imul(temp, (temp ^ 1)) >>> 8); } function getInt8(number) { return number & 0xFF; } function getInt32(number) { return number & 0xFFFFFFFF; } /* Copyright (c) 2022 Gildas Lormeau. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of the authors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ const COMPRESSION_FORMAT = "deflate-raw"; class DeflateStream extends TransformStream { constructor(options, { chunkSize, CompressionStream, CompressionStreamNative }) { super({}); const { compressed, encrypted, useCompressionStream, zipCrypto, signed, level } = options; const stream = this; let crc32Stream, encryptionStream; let readable = filterEmptyChunks(super.readable); if ((!encrypted || zipCrypto) && signed) { crc32Stream = new Crc32Stream(); readable = pipeThrough(readable, crc32Stream); } if (compressed) { readable = pipeThroughCommpressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStreamNative, CompressionStream); } if (encrypted) { if (zipCrypto) { readable = pipeThrough(readable, new ZipCryptoEncryptionStream(options)); } else { encryptionStream = new AESEncryptionStream(options); readable = pipeThrough(readable, encryptionStream); } } setReadable(stream, readable, () => { let signature; if (encrypted && !zipCrypto) { signature = encryptionStream.signature; } if ((!encrypted || zipCrypto) && signed) { signature = new DataView(crc32Stream.value.buffer).getUint32(0); } stream.signature = signature; }); } } class InflateStream extends TransformStream { constructor(options, { chunkSize, DecompressionStream, DecompressionStreamNative }) { super({}); const { zipCrypto, encrypted, signed, signature, compressed, useCompressionStream } = options; let crc32Stream, decryptionStream; let readable = filterEmptyChunks(super.readable); if (encrypted) { if (zipCrypto) { readable = pipeThrough(readable, new ZipCryptoDecryptionStream(options)); } else { decryptionStream = new AESDecryptionStream(options); readable = pipeThrough(readable, decryptionStream); } } if (compressed) { readable = pipeThroughCommpressionStream(readable, useCompressionStream, { chunkSize }, DecompressionStreamNative, DecompressionStream); } if ((!encrypted || zipCrypto) && signed) { crc32Stream = new Crc32Stream(); readable = pipeThrough(readable, crc32Stream); } setReadable(this, readable, () => { if ((!encrypted || zipCrypto) && signed) { const dataViewSignature = new DataView(crc32Stream.value.buffer); if (signature != dataViewSignature.getUint32(0, false)) { throw new Error(ERR_INVALID_SIGNATURE); } } }); } } function filterEmptyChunks(readable) { return pipeThrough(readable, new TransformStream({ transform(chunk, controller) { if (chunk && chunk.length) { controller.enqueue(chunk); } } })); } function setReadable(stream, readable, flush) { readable = pipeThrough(readable, new TransformStream({ flush })); Object.defineProperty(stream, "readable", { get() { return readable; } }); } function pipeThroughCommpressionStream(readable, useCompressionStream, options, CodecStreamNative, CodecStream) { try { const CompressionStream = useCompressionStream && CodecStreamNative ? CodecStreamNative : CodecStream; readable = pipeThrough(readable, new CompressionStream(COMPRESSION_FORMAT, options)); } catch (error) { if (useCompressionStream) { try { readable = pipeThrough(readable, new CodecStream(COMPRESSION_FORMAT, options)); } catch (error) { return readable; } } else { return readable; } } return readable; } function pipeThrough(readable, transformStream) { return readable.pipeThrough(transformStream); } /* Copyright (c) 2022 Gildas Lormeau. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of the authors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ const MESSAGE_EVENT_TYPE = "message"; const MESSAGE_START = "start"; const MESSAGE_PULL = "pull"; const MESSAGE_DATA = "data"; const MESSAGE_ACK_DATA = "ack"; const MESSAGE_CLOSE = "close"; const CODEC_DEFLATE = "deflate"; const CODEC_INFLATE = "inflate"; class CodecStream extends TransformStream { constructor(options, config) { super({}); const codec = this; const { codecType } = options; let Stream; if (codecType.startsWith(CODEC_DEFLATE)) { Stream = DeflateStream; } else if (codecType.startsWith(CODEC_INFLATE)) { Stream = InflateStream; } let outputSize = 0; let inputSize = 0; const stream = new Stream(options, config); const readable = super.readable; const inputSizeStream = new TransformStream({ transform(chunk, controller) { if (chunk && chunk.length) { inputSize += chunk.length; controller.enqueue(chunk); } }, flush() { Object.assign(codec, { inputSize }); } }); const outputSizeStream = new TransformStream({ transform(chunk, controller) { if (chunk && chunk.length) { outputSize += chunk.length; controller.enqueue(chunk); } }, flush() { const { signature } = stream; Object.assign(codec, { signature, outputSize, inputSize }); } }); Object.defineProperty(codec, "readable", { get() { return readable.pipeThrough(inputSizeStream).pipeThrough(stream).pipeThrough(outputSizeStream); } }); } } class ChunkStream extends TransformStream { constructor(chunkSize) { let pendingChunk; super({ transform, flush(controller) { if (pendingChunk && pendingChunk.length) { controller.enqueue(pendingChunk); } } }); function transform(chunk, controller) { if (pendingChunk) { const newChunk = new Uint8Array(pendingChunk.length + chunk.length); newChunk.set(pendingChunk); newChunk.set(chunk, pendingChunk.length); chunk = newChunk; pendingChunk = null; } if (chunk.length > chunkSize) { controller.enqueue(chunk.slice(0, chunkSize)); transform(chunk.slice(chunkSize), controller); } else { pendingChunk = chunk; } } } } /* Copyright (c) 2022 Gildas Lormeau. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of the authors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // deno-lint-ignore valid-typeof let WEB_WORKERS_SUPPORTED = typeof Worker != UNDEFINED_TYPE; class CodecWorker { constructor(workerData, { readable, writable }, { options, config, streamOptions, useWebWorkers, transferStreams, scripts }, onTaskFinished) { const { signal } = streamOptions; Object.assign(workerData, { busy: true, readable: readable .pipeThrough(new ChunkStream(config.chunkSize)) .pipeThrough(new ProgressWatcherStream(readable, streamOptions), { signal }), writable, options: Object.assign({}, options), scripts, transferStreams, terminate() { return new Promise(resolve => { const { worker, busy } = workerData; if (worker) { if (busy) { workerData.resolveTerminated = resolve; } else { worker.terminate(); resolve(); } workerData.interface = null; } else { resolve(); } }); }, onTaskFinished() { const { resolveTerminated } = workerData; if (resolveTerminated) { workerData.resolveTerminated = null; workerData.terminated = true; workerData.worker.terminate(); resolveTerminated(); } workerData.busy = false; onTaskFinished(workerData); } }); return (useWebWorkers && WEB_WORKERS_SUPPORTED ? createWebWorkerInterface : createWorkerInterface)(workerData, config); } } class ProgressWatcherStream extends TransformStream { constructor(readableSource, { onstart, onprogress, size, onend }) { let chunkOffset = 0; super({ async start() { if (onstart) { await callHandler(onstart, size); } }, async transform(chunk, controller) { chunkOffset += chunk.length; if (onprogress) { await callHandler(onprogress, chunkOffset, size); } controller.enqueue(chunk); }, async flush() { readableSource.size = chunkOffset; if (onend) { await callHandler(onend, chunkOffset); } } }); } } async function callHandler(handler, ...parameters) { try { await handler(...parameters); } catch (_error) { // ignored } } function createWorkerInterface(workerData, config) { return { run: () => runWorker$1(workerData, config) }; } function createWebWorkerInterface(workerData, config) { const { baseURL, chunkSize } = config; if (!workerData.interface) { let worker; try { worker = getWebWorker(workerData.scripts[0], baseURL, workerData); } catch (error) { WEB_WORKERS_SUPPORTED = false; return createWorkerInterface(workerData, config); } Object.assign(workerData, { worker, interface: { run: () => runWebWorker(workerData, { chunkSize }) } }); } return workerData.interface; } async function runWorker$1({ options, readable, writable, onTaskFinished }, config) { try { const codecStream = new CodecStream(options, config); await readable.pipeThrough(codecStream).pipeTo(writable, { preventClose: true, preventAbort: true }); const { signature, inputSize, outputSize } = codecStream; return { signature, inputSize, outputSize }; } finally { onTaskFinished(); } } async function runWebWorker(workerData, config) { let resolveResult, rejectResult; const result = new Promise((resolve, reject) => { resolveResult = resolve; rejectResult = reject; }); Object.assign(workerData, { reader: null, writer: null, resolveResult, rejectResult, result }); const { readable, options, scripts } = workerData; const { writable, closed } = watchClosedStream(workerData.writable); const streamsTransferred = sendMessage({ type: MESSAGE_START, scripts: scripts.slice(1), options, config, readable, writable }, workerData); if (!streamsTransferred) { Object.assign(workerData, { reader: readable.getReader(), writer: writable.getWriter() }); } const resultValue = await result; if (!streamsTransferred) { await writable.getWriter().close(); } await closed; return resultValue; } function watchClosedStream(writableSource) { let resolveStreamClosed; const closed = new Promise(resolve => resolveStreamClosed = resolve); const writable = new WritableStream({ async write(chunk) { const writer = writableSource.getWriter(); await writer.ready; await writer.write(chunk); writer.releaseLock(); }, close() { resolveStreamClosed(); }, abort(reason) { const writer = writableSource.getWriter(); return writer.abort(reason); } }); return { writable, closed }; } let classicWorkersSupported = true; let transferStreamsSupported = true; function getWebWorker(url, baseURL, workerData) { const workerOptions = { type: "module" }; let scriptUrl, worker; // deno-lint-ignore valid-typeof if (typeof url == FUNCTION_TYPE) { url = url(); } try { scriptUrl = new URL(url, baseURL); } catch (_error) { scriptUrl = url; } if (classicWorkersSupported) { try { worker = new Worker(scriptUrl); } catch (_error) { classicWorkersSupported = false; worker = new Worker(scriptUrl, workerOptions); } } else { worker = new Worker(scriptUrl, workerOptions); } worker.addEventListener(MESSAGE_EVENT_TYPE, event => onMessage(event, workerData)); return worker; } function sendMessage(message, { worker, writer, onTaskFinished, transferStreams }) { try { let { value, readable, writable } = message; const transferables = []; if (value) { if (value.byteLength < value.buffer.byteLength) { message.value = value.buffer.slice(0, value.byteLength); } else { message.value = value.buffer; } transferables.push(message.value); } if (transferStreams && transferStreamsSupported) { if (readable) { transferables.push(readable); } if (writable) { transferables.push(writable); } } else { message.readable = message.writable = null; } if (transferables.length) { try { worker.postMessage(message, transferables); return true; } catch (_error) { transferStreamsSupported = false; message.readable = message.writable = null; worker.postMessage(message); } } else { worker.postMessage(message); } } catch (error) { if (writer) { writer.releaseLock(); } onTaskFinished(); throw error; } } async function onMessage({ data }, workerData) { const { type, value, messageId, result, error } = data; const { reader, writer, resolveResult, rejectResult, onTaskFinished } = workerData; try { if (error) { const { message, stack, code, name } = error; const responseError = new Error(message); Object.assign(responseError, { stack, code, name }); close(responseError); } else { if (type == MESSAGE_PULL) { const { value, done } = await reader.read(); sendMessage({ type: MESSAGE_DATA, value, done, messageId }, workerData); } if (type == MESSAGE_DATA) { await writer.ready; await writer.write(new Uint8Array(value)); sendMessage({ type: MESSAGE_ACK_DATA, messageId }, workerData); } if (type == MESSAGE_CLOSE) { close(null, result); } } } catch (error) { sendMessage({ type: MESSAGE_CLOSE, messageId }, workerData); close(error); } function close(error, result) { if (error) { rejectResult(error); } else { resolveResult(result); } if (writer) { writer.releaseLock(); } onTaskFinished(); } } /* Copyright (c) 2022 Gildas Lormeau. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of the authors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ let pool = []; const pendingRequests = []; let indexWorker = 0; async function runWorker(stream, workerOptions) { const { options, config } = workerOptions; const { transferStreams, useWebWorkers, useCompressionStream, codecType, compressed, signed, encrypted } = options; const { workerScripts, maxWorkers } = config; workerOptions.transferStreams = transferStreams || transferStreams === UNDEFINED_VALUE; const streamCopy = !compressed && !signed && !encrypted && !workerOptions.transferStreams; workerOptions.useWebWorkers = !streamCopy && (useWebWorkers || (useWebWorkers === UNDEFINED_VALUE && config.useWebWorkers)); workerOptions.scripts = workerOptions.useWebWorkers && workerScripts ? workerScripts[codecType] : []; options.useCompressionStream = useCompressionStream || (useCompressionStream === UNDEFINED_VALUE && config.useCompressionStream); return (await getWorker()).run(); async function getWorker() { const workerData = pool.find(workerData => !workerData.busy); if (workerData) { clearTerminateTimeout(workerData); return new CodecWorker(workerData, stream, workerOptions, onTaskFinished); } else if (pool.length < maxWorkers) { const workerData = { indexWorker }; indexWorker++; pool.push(workerData); return new CodecWorker(workerData, stream, workerOptions, onTaskFinished); } else { return new Promise(resolve => pendingRequests.push({ resolve, stream, workerOptions })); } } function onTaskFinished(workerData) { if (pendingRequests.length) { const [{ resolve, stream, workerOptions }] = pendingRequests.splice(0, 1); resolve(new CodecWorker(workerData, stream, workerOptions, onTaskFinished)); } else if (workerData.worker) { clearTerminateTimeout(workerData); terminateWorker(workerData, workerOptions); } else { pool = pool.filter(data => data != workerData); } } } function terminateWorker(workerData, workerOptions) { const { config } = workerOptions; const { terminateWorkerTimeout } = config; if (Number.isFinite(terminateWorkerTimeout) && terminateWorkerTimeout >= 0) { if (workerData.terminated) { workerData.terminated = false; } else { workerData.terminateTimeout = setTimeout(async () => { pool = pool.filter(data => data != workerData); try { await workerData.terminate(); } catch (_error) { // ignored } }, terminateWorkerTimeout); } } } function clearTerminateTimeout(workerData) { const { terminateTimeout } = workerData; if (terminateTimeout) { clearTimeout(terminateTimeout); workerData.terminateTimeout = null; } } function e(e){const t=()=>URL.createObjectURL(new Blob(['const{Array:e,Object:t,Number:n,Math:r,Error:s,Uint8Array:i,Uint16Array:o,Uint32Array:c,Int32Array:f,Map:a,DataView:l,Promise:u,TextEncoder:w,crypto:h,postMessage:d,TransformStream:p,ReadableStream:y,WritableStream:m,CompressionStream:b,DecompressionStream:g}=self,k=void 0,v="undefined",S="function";class z{constructor(e){return class extends p{constructor(t,n){const r=new e(n);super({transform(e,t){t.enqueue(r.append(e))},flush(e){const t=r.flush();t&&e.enqueue(t)}})}}}}const C=[];for(let e=0;256>e;e++){let t=e;for(let e=0;8>e;e++)1&t?t=t>>>1^3988292384:t>>>=1;C[e]=t}class x{constructor(e){this.t=e||-1}append(e){let t=0|this.t;for(let n=0,r=0|e.length;r>n;n++)t=t>>>8^C[255&(t^e[n])];this.t=t}get(){return~this.t}}class A extends p{constructor(){let e;const t=new x;super({transform(e,n){t.append(e),n.enqueue(e)},flush(){const n=new i(4);new l(n.buffer).setUint32(0,t.get()),e.value=n}}),e=this}}const _={concat(e,t){if(0===e.length||0===t.length)return e.concat(t);const n=e[e.length-1],r=_.i(n);return 32===r?e.concat(t):_.o(t,r,0|n,e.slice(0,e.length-1))},l(e){const t=e.length;if(0===t)return 0;const n=e[t-1];return 32*(t-1)+_.i(n)},u(e,t){if(32*e.lengthn[++_];)y-=n[_];if(P=1<
${workURLs.map((r, index) => `