mirror of
https://github.com/kiwix/kiwix-js-pwa.git
synced 2025-09-08 19:57:46 -04:00
parent
0c833bae77
commit
7c40edd189
@ -2,7 +2,9 @@
|
||||
|
||||
## In-progress release 3.2.2
|
||||
|
||||
* FEATURE: Add security dialogue on opening a ZIM for the first time in ServiceWorker mode
|
||||
* UPDATE: Rename JQuery mode to Restricted mode
|
||||
* FIX: Cached last page sometimes overwrites new ZIM landing page when switching from Restricted mode
|
||||
* FIX: Display of open/close marker with h5 and h6 headings in Wikimedia ZIMs
|
||||
* FIX: Inability to print HTML books in Gutenberg ZIMs
|
||||
* FIX: Bug in JQuery mode which made all images load as manual display areas in some non-Wikimedia ZIMs
|
||||
|
@ -1252,6 +1252,11 @@
|
||||
<span class="checkmark"></span>
|
||||
<b>Disable drag-and-drop</b> (in case it is causing anomalies)
|
||||
</label>
|
||||
<label class="checkbox" data-i18n-tip="configure-expert-enable-source-verification-tip" title="Warning: Some ZIM archives from untrusted sources could run malicious code in your browser. This can be prevented by using Restricted mode, which cannot run active content from the ZIM. Highly dynamic ZIMs will probably fail in Restricted mode, but ZIMs with largely static content should work. If you trust the source of all of your ZIMs, then disabling this option will use ServiceWorker mode by default, if available.">
|
||||
<input type="checkbox" name="disableFileVerification" id="enableSourceVerificationCheck" >
|
||||
<span data-i18n="configure-expert-enable-source-verification-check-box" class="checkmark"></span>
|
||||
<b>Enable source verification of new files</b> (<i>recommended</i>: you will only be prompted the first time you open a ZIM)
|
||||
</label>
|
||||
<div id="expressPortInputDiv" style="display: none;" title="If you allowed network access on startup, you can access this app from any local browser by going to localhost:port in the browser address bar (e.g. http://localhost:3000).">
|
||||
<b>Customize the localhost port when accessing this app from a browser</b>:<br />
|
||||
<label>
|
||||
|
182
www/js/app.js
182
www/js/app.js
@ -464,7 +464,7 @@ function printIntercept () {
|
||||
return uiUtil.systemAlert('Sorry, we could not find a document to print! Please load one first.', 'Warning');
|
||||
}
|
||||
if (params.contentInjectionMode === 'serviceworker') {
|
||||
// Re-establishe lastPageVisit because it is not always set, for example with dynamic loads, in SW mode
|
||||
// Re-establish lastPageVisit because it is not always set, for example with dynamic loads, in SW mode
|
||||
params.lastPageVisit = articleDocument.location.href.replace(/^.+\/([^/]+\.[zZ][iI][mM]\w?\w?)\/([CA]\/.*$)/, function (m0, zimName, zimURL) {
|
||||
return decodeURI(zimURL) + '@kiwixKey@' + decodeURI(zimName);
|
||||
});
|
||||
@ -1689,15 +1689,27 @@ document.querySelectorAll('input[name="contentInjectionMode"][type="radio"]').fo
|
||||
}
|
||||
// Do the necessary to enable or disable the Service Worker
|
||||
setContentInjectionMode(this.value);
|
||||
// If we're in a PWA UWP app, warn the user that this does not disable the PWA
|
||||
if (this.value === 'jquery' && /^http/i.test(window.location.protocol) && /UWP\|PWA/.test(params.appType) &&
|
||||
params.allowInternetAccess === 'true') {
|
||||
uiUtil.systemAlert(
|
||||
'<p>Please note that switching content injection mode does not revert to local code.</p>' +
|
||||
'<p>If you wish to exit the PWA, you will need to turn off "Allow Internet access?" above.</p>'
|
||||
);
|
||||
|
||||
/** DEV: PLEASE NOTE THAT "jQuery mode" HAS NOW CHANGED to "Restricted mode", but we still use "jquery" in code */
|
||||
|
||||
// Actions that must be completed after switch to Restricted mode
|
||||
if (this.value === 'jquery') {
|
||||
// Hide the source verification option
|
||||
document.getElementById('enableSourceVerificationCheck').style.display = 'none';
|
||||
// If we're in a PWA UWP app, warn the user that this does not disable the PWA
|
||||
if (/^http/i.test(window.location.protocol) && /UWP\|PWA/.test(params.appType) &&
|
||||
params.allowInternetAccess === 'true') {
|
||||
uiUtil.systemAlert(
|
||||
'<p>Please note that switching content injection mode does not revert to local code.</p>' +
|
||||
'<p>If you wish to exit the PWA, you will need to turn off "Allow Internet access?" above.</p>'
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.value === 'serviceworker') {
|
||||
document.getElementById('enableSourceVerificationCheck').style.display = '';
|
||||
if (appstate.selectedArchive.isReady() && !(settingsStore.getItem('trustedZimFiles').includes(appstate.selectedArchive.file.name)) && params.sourceVerification) {
|
||||
verifyLoadedArchive(appstate.selectedArchive);
|
||||
}
|
||||
if (params.manipulateImages || params.allowHTMLExtraction) {
|
||||
if (!appstate.wikimediaZimLoaded) {
|
||||
var message = 'Please note that we are disabling "Image manipulation" and/or "Download or open current article" features, as these options ' +
|
||||
@ -1855,6 +1867,12 @@ document.getElementById('disableDragAndDropCheck').addEventListener('change', fu
|
||||
}
|
||||
});
|
||||
});
|
||||
// Source verification is only makes sense in SW mode as doing the same in jQuery mode is redundant.
|
||||
document.getElementById('enableSourceVerificationCheck').style.display = params.contentInjectionMode === ('serviceworker' || 'serviceworkerlocal') ? 'block' : 'none';
|
||||
document.getElementById('enableSourceVerificationCheck').addEventListener('change', function () {
|
||||
params.sourceVerification = this.checked;
|
||||
settingsStore.setItem('sourceVerification', this.checked, Infinity);
|
||||
});
|
||||
document.getElementById('hideActiveContentWarningCheck').addEventListener('change', function () {
|
||||
params.hideActiveContentWarning = this.checked;
|
||||
settingsStore.setItem('hideActiveContentWarning', params.hideActiveContentWarning, Infinity);
|
||||
@ -2418,12 +2436,12 @@ function switchCSSTheme () {
|
||||
}, 100);
|
||||
// If the interval has not succeeded after 3 seconds, give up
|
||||
if (zimitIframe && document.getElementById('configuration').style.display === 'none') {
|
||||
setTimeout(function () {
|
||||
articleContainer.style.display = '';
|
||||
zimitIframe.style.display = '';
|
||||
setTimeout(function (zimitf, articleC) {
|
||||
articleC.style.display = '';
|
||||
zimitf.style.display = '';
|
||||
clearInterval(interval);
|
||||
window.dispatchEvent(new Event('resize')); // Force repaint
|
||||
}, 3000);
|
||||
}, 3000, zimitIframe, articleContainer);
|
||||
}
|
||||
} else if (document.getElementById('configuration').style.display === 'none') {
|
||||
// We're dealing with a light style, so we just display it
|
||||
@ -4067,6 +4085,38 @@ function setLocalArchiveFromFileList (files, fromArchiveList) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given archive and switches contentInjectionMode accourdingly
|
||||
* Code to undertake the verification adapted from kiwix/kiwix-js #1192 kindly authored by @Greeshmanth1909
|
||||
*
|
||||
* @param {Object} archive The archive that needs verification
|
||||
*
|
||||
*/
|
||||
function verifyLoadedArchive (archive) {
|
||||
return uiUtil.systemAlert('<p>Is this ZIM archive from a trusted source?</p><p style="border: 1px solid;padding:5px;">' +
|
||||
'Name: <b>' + archive.file.name + '</b><br />' +
|
||||
'Creator: <b>' + archive.creator + '</b><br />' +
|
||||
'Publisher: <b>' + archive.publisher + '</b><br />' +
|
||||
'Scraper: <b>' + archive.scraper + '</b><br />' +
|
||||
'</p><p><b><i>Warning: above data can easily be spoofed!</i></b></p>' +
|
||||
'</p><p>If you do not trust the source, you can still read the ZIM file in Restricted mode. Closing this window also opens the file in Restricted mode.</p>' +
|
||||
'<p><i>If you mark the file as trusted, this alert will not show again.</i> (Security checks can be disabled in Expert Settings.)</p>',
|
||||
'Security alert!', true, 'Open in Restricted mode', 'Trust source').then(response => {
|
||||
if (response) {
|
||||
params.contentInjectionMode = 'serviceworker';
|
||||
var trustedZimFiles = settingsStore.getItem('trustedZimFiles');
|
||||
var updatedTrustedZimFiles = trustedZimFiles + archive.file.name + '|';
|
||||
settingsStore.setItem('trustedZimFiles', updatedTrustedZimFiles, Infinity);
|
||||
// Change radio buttons accordingly
|
||||
document.getElementById('serviceworkerModeRadio').checked = true;
|
||||
} else {
|
||||
// Switch to Restricted mode
|
||||
params.contentInjectionMode = 'jquery';
|
||||
document.getElementById('jQueryModeRadio').checked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions to be run immediately after the archive is loaded
|
||||
*
|
||||
@ -4185,27 +4235,57 @@ function archiveReadyCallback (archive) {
|
||||
}
|
||||
// This ensures the correct icon is set for the newly loaded archive
|
||||
cssUIThemeGetOrSet(params.cssUITheme);
|
||||
if (params.rescan) {
|
||||
document.getElementById('btnConfigure').click();
|
||||
setTimeout(function () {
|
||||
var displayArchive = function () {
|
||||
if (params.rescan) {
|
||||
document.getElementById('btnConfigure').click();
|
||||
params.rescan = false;
|
||||
}, 100);
|
||||
} else {
|
||||
if (typeof Windows === 'undefined' && typeof window.showOpenFilePicker !== 'function' && !params.useOPFS && !window.dialog) {
|
||||
document.getElementById('instructions').style.display = 'none';
|
||||
setTimeout(function () {
|
||||
document.getElementById('btnConfigure').click();
|
||||
params.rescan = false;
|
||||
}, 100);
|
||||
} else {
|
||||
document.getElementById('openLocalFiles').style.display = 'none';
|
||||
document.getElementById('rescanStorage').style.display = 'block';
|
||||
}
|
||||
document.getElementById('usage').style.display = 'none';
|
||||
if (params.rememberLastPage && ~params.lastPageVisit.indexOf(params.storedFile.replace(/\.zim(\w\w)?$/, ''))) {
|
||||
var lastPage = params.lastPageVisit.replace(/@kiwixKey@.+/, '');
|
||||
goToArticle(lastPage);
|
||||
} else {
|
||||
document.getElementById('btnHome').click();
|
||||
if (typeof Windows === 'undefined' && typeof window.showOpenFilePicker !== 'function' && !params.useOPFS && !window.dialog) {
|
||||
document.getElementById('instructions').style.display = 'none';
|
||||
} else {
|
||||
document.getElementById('openLocalFiles').style.display = 'none';
|
||||
document.getElementById('rescanStorage').style.display = 'block';
|
||||
}
|
||||
document.getElementById('usage').style.display = 'none';
|
||||
if (params.rememberLastPage && ~params.lastPageVisit.indexOf(params.storedFile.replace(/\.zim(\w\w)?$/, ''))) {
|
||||
var lastPage = params.lastPageVisit.replace(/@kiwixKey@.+/, '');
|
||||
goToArticle(lastPage);
|
||||
} else {
|
||||
document.getElementById('btnHome').click();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set contentInjectionMode to serviceWorker when opening a new archive in case the user switched to Restricted mode/jQuery Mode when opening the previous archive
|
||||
if (params.contentInjectionMode === 'jquery') {
|
||||
params.contentInjectionMode = settingsStore.getItem('contentInjectionMode');
|
||||
// Change the radio buttons accordingly
|
||||
switch (settingsStore.getItem('contentInjectionMode')) {
|
||||
case 'serviceworker':
|
||||
document.getElementById('serviceworkerModeRadio').checked = true;
|
||||
// In case we atuo-switched off assetsCache due to switch to Restricted mode, we need to reset
|
||||
params.assetsCache = settingsStore.getItem('asetsCache') !== 'false';
|
||||
break;
|
||||
case 'serviceworkerlocal':
|
||||
document.getElementById('serviceworkerLocalModeRadio').checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (settingsStore.getItem('trustedZimFiles') === null) {
|
||||
settingsStore.setItem('trustedZimFiles', '', Infinity);
|
||||
}
|
||||
if (params.sourceVerification && (params.contentInjectionMode === 'serviceworker' || params.contentInjectionMode === 'serviceworkerlocal')) {
|
||||
// Check if source of the zim file can be trusted.
|
||||
if (!(settingsStore.getItem('trustedZimFiles').includes(archive.file.name))) {
|
||||
verifyLoadedArchive(archive).then(function () {
|
||||
displayArchive();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
displayArchive();
|
||||
}
|
||||
|
||||
function loadPackagedArchive () {
|
||||
@ -5119,6 +5199,21 @@ var articleLoadedSW = function (dirEntry, container) {
|
||||
uiUtil.showSlidingUIElements();
|
||||
var doc = articleWindow ? articleWindow.document : null;
|
||||
articleDocument = doc;
|
||||
var mimeType = dirEntry.getMimetype();
|
||||
// If we've successfully loaded an HTML document...
|
||||
if (doc && /\bx?html/i.test(mimeType)) {
|
||||
if (params.rememberLastPage) {
|
||||
params.lastPageVisit = dirEntry.namespace + '/' + dirEntry.url + '@kiwixKey@' + appstate.selectedArchive.file.name;
|
||||
} else {
|
||||
params.lastPageVisit = '';
|
||||
}
|
||||
// Turn off failsafe for SW mode
|
||||
settingsStore.setItem('lastPageLoad', 'OK', Infinity);
|
||||
settingsStore.setItem('lastPageVisit', params.lastPageVisit, Infinity);
|
||||
// Set or clear the ZIM store of last page
|
||||
var lastPage = params.rememberLastPage ? dirEntry.namespace + '/' + dirEntry.url : '';
|
||||
settingsStore.setItem(appstate.selectedArchive.file.name, lastPage, Infinity);
|
||||
}
|
||||
var docBody = doc ? doc.body : null;
|
||||
if (docBody) {
|
||||
// Trap clicks in the iframe to enable us to work around the sandbox when opening external links and PDFs
|
||||
@ -5133,7 +5228,6 @@ var articleLoadedSW = function (dirEntry, container) {
|
||||
listenForSearchKeys();
|
||||
}
|
||||
// Note that switchCSSTheme() requires access to params.lastPageVisit
|
||||
params.lastPageVisit = dirEntry.namespace + '/' + dirEntry.url + '@kiwixKey@' + appstate.selectedArchive.file.name;
|
||||
if (!appstate.isReplayWorkerAvailable) switchCSSTheme(); // Gets called in articleLoader for replay_iframe
|
||||
if (appstate.selectedArchive.zimType === 'open') {
|
||||
// Set relative font size + Stackexchange-family multiplier
|
||||
@ -5183,12 +5277,6 @@ var articleLoadedSW = function (dirEntry, container) {
|
||||
resizeIFrame();
|
||||
}, 200);
|
||||
}
|
||||
// Turn off failsafe for SW mode
|
||||
settingsStore.setItem('lastPageLoad', 'OK', Infinity);
|
||||
if (!appstate.isReplayWorkerAvailable) {
|
||||
// Because this is loading within docBody, it should only get set for HTML documents
|
||||
if (params.rememberLastPage) settingsStore.setItem('lastPageVisit', params.lastPageVisit, Infinity);
|
||||
}
|
||||
uiUtil.clearSpinner();
|
||||
// If we reloaded the page to print the desktop style, we need to return to the printIntercept dialogue
|
||||
if (params.printIntercept) printIntercept();
|
||||
@ -5205,7 +5293,10 @@ var articleLoadedSW = function (dirEntry, container) {
|
||||
if (dirEntry) uiUtil.makeReturnLink(dirEntry.getTitleOrUrl());
|
||||
params.isLandingPage = false;
|
||||
} else {
|
||||
loaded = false;
|
||||
// If we havent' loaded a text-type document, we probably haven't finished loading
|
||||
if (!/^text\//i.test(mimeType)) {
|
||||
loaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Show spinner when the article unloads
|
||||
@ -6253,7 +6344,8 @@ function displayArticleContentInContainer (dirEntry, htmlArticle) {
|
||||
if (downloadAlert) downloadAlert.style.display = 'none';
|
||||
|
||||
// Code below will run after we have written the new article to the articleContainer
|
||||
var articleLoaded = params.contentInjectionMode === 'serviceworker' ? function () {} : function () {
|
||||
var articleLoaded = function () {
|
||||
if (params.contentInjectionMode === 'serviceworker') return;
|
||||
// Set a global error handler for articleWindow
|
||||
articleWindow.onerror = function (msg, url, line, col, error) {
|
||||
console.error('Error caught in ZIM contents [' + url + ':' + line + ']:\n' + msg, error);
|
||||
@ -6379,17 +6471,9 @@ function displayArticleContentInContainer (dirEntry, htmlArticle) {
|
||||
// Make sure the article area is displayed
|
||||
setTab();
|
||||
checkToolbar();
|
||||
var showArticle = function () {
|
||||
articleDocument.bgcolor = '';
|
||||
docBody.style.display = 'block';
|
||||
};
|
||||
if ('MSBlobBuilder' in window) {
|
||||
// For legacy MS browsers, including UWP, delay causes blank screen on slow systems
|
||||
showArticle();
|
||||
} else {
|
||||
// For Chromium browsers a small delay greatly improves composition
|
||||
setTimeout(showArticle, 80);
|
||||
}
|
||||
// Show the article
|
||||
articleDocument.bgcolor = '';
|
||||
docBody.style.display = 'block';
|
||||
// Jump to any anchor parameter
|
||||
if (anchorParameter) {
|
||||
var target = articleWindow.document.getElementById(anchorParameter);
|
||||
|
@ -110,6 +110,7 @@ params['rightClickType'] = getSetting('rightClickType'); // 'single|double|false
|
||||
params['navButtonsPos'] = getSetting('navButtonsPos') || 'bottom'; // 'top|bottom' A setting that determines where the back-forward nav buttons appear
|
||||
params['useOPFS'] = getSetting('useOPFS') === true; // A setting that determines whether to use OPFS (experimental)
|
||||
params['useLegacyZimitSupport'] = getSetting('useLegacyZimitSupport') === true; // A setting that determines whether to force the use of legacy Zimit support
|
||||
params['sourceVerification'] = params.contentInjectionMode === 'serviceworker' ? (getSetting('sourceVerification') === null ? true : getSetting('sourceVerification')) : false; // Sets a boolean indicating weather a user trusts the source of zim files
|
||||
|
||||
// Do not touch these values unless you know what they do! Some are global variables, some are set programmatically
|
||||
params['cacheAPI'] = 'kiwixjs-assetsCache'; // Set the global Cache API database or cache name here, and synchronize with Service Worker
|
||||
@ -251,6 +252,7 @@ document.getElementById('rememberLastPageCheck').checked = params.rememberLastPa
|
||||
document.getElementById('displayFileSelectorsCheck').checked = params.showFileSelectors;
|
||||
document.getElementById('hideActiveContentWarningCheck').checked = params.hideActiveContentWarning;
|
||||
document.getElementById('useLibzimReaderCheck').checked = params.useLibzim;
|
||||
document.getElementById('enableSourceVerificationCheck').checked = getSetting('sourceVerification') === null ? true : getSetting('sourceVerification');
|
||||
document.getElementById('useLegacyZimitSupportCheck').checked = params.useLegacyZimitSupport;
|
||||
document.getElementById('alphaCharTxt').value = params.alphaChar;
|
||||
document.getElementById('omegaCharTxt').value = params.omegaChar;
|
||||
|
@ -80,7 +80,8 @@ function ZIMArchive (storage, path, callbackReady, callbackError) {
|
||||
// Further metadata are added in the background below, and can be accessed later
|
||||
return Promise.all([
|
||||
that.addMetadataToZIMFile('Creator'),
|
||||
that.addMetadataToZIMFile('Language')
|
||||
that.addMetadataToZIMFile('Language'),
|
||||
that.addMetadataToZIMFile('Publisher')
|
||||
]).then(function () {
|
||||
console.debug('ZIMArchive ready, metadata will be added in the background');
|
||||
// Add non-time-critical metadata to archive in background so as not to delay opening of the archive
|
||||
@ -92,7 +93,6 @@ function ZIMArchive (storage, path, callbackReady, callbackError) {
|
||||
that.addMetadataToZIMFile('Date'),
|
||||
that.addMetadataToZIMFile('Description'),
|
||||
that.addMetadataToZIMFile('Name'),
|
||||
that.addMetadataToZIMFile('Publisher'),
|
||||
that.addMetadataToZIMFile('Source'),
|
||||
that.addMetadataToZIMFile('Title')
|
||||
]).then(function () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user