313 lines
11 KiB
JavaScript
313 lines
11 KiB
JavaScript
// NibblePoker - Excel Password Remover 2.0
|
|
// Author: Herwin Bozet (@NibblePoker)
|
|
// License: Public Domain (This code)
|
|
|
|
// Implied Globals: JSZip
|
|
if(JSZip == null) {
|
|
alert("JSZip isn't available !");
|
|
}
|
|
|
|
import {initCore} from "../../js/nibblepoker-core.mjs"
|
|
import {cloneTemplate} from "../../js/nibblepoker-template.mjs"
|
|
|
|
export const excelFileRegex = /^.*\.xls[xm]$/gi;
|
|
export const excelWorksheetRegex = /^xl\/worksheets\/.*.xml$/gi;
|
|
|
|
/**
|
|
* Checks if the given filename appears to be for an Excel file.
|
|
* @param fileName {string} Filename to be checked
|
|
* @returns {boolean} `true` if it appears to be an Excel file, `false` otherwise.
|
|
*/
|
|
export function isExcelExtension(fileName) {
|
|
return fileName.match(excelFileRegex) !== null;
|
|
}
|
|
|
|
// Tool-centric stuff
|
|
{
|
|
initCore();
|
|
|
|
//let outputZip;
|
|
//let outputZipFilename = "default-filename.error.zip";
|
|
//let filesTotalCount = 0;
|
|
//let filesProcessedCount = 0;
|
|
//let passwordsRemoved = 0;
|
|
|
|
/** @type {string} */
|
|
const appletId = "excel-password-remover";
|
|
|
|
/** @type {HTMLInputElement} */
|
|
const eFileInput = document.querySelector(`input[type=file]#${appletId}-input-file`);
|
|
/** @type {HTMLButtonElement} */
|
|
const eFileInputClearButton = document.querySelector(`button#${appletId}-input-file-reset`);
|
|
|
|
/** @type {HTMLElement} */
|
|
const eResultEmptyText = document.querySelector(`#${appletId}-details-empty`);
|
|
/** @type {HTMLElement} */
|
|
const eResultPopulatedText = document.querySelector(`#${appletId}-details-populated`);
|
|
|
|
/** @type {HTMLElement} */
|
|
const eResultContainer = document.querySelector(`#${appletId}-result-container`);
|
|
|
|
/** @type {HTMLTemplateElement} */
|
|
const eSuccessTemplate = document.querySelector(`template#tmpl-success-root`);
|
|
/** @type {HTMLTemplateElement} */
|
|
const eWarningTemplate = document.querySelector(`template#tmpl-warning-root`);
|
|
/** @type {HTMLTemplateElement} */
|
|
const eErrorTemplate = document.querySelector(`template#tmpl-error-root`);
|
|
|
|
/** @type {HTMLButtonElement} */
|
|
const eFileDownloadAllButton = document.querySelector(`button#${appletId}-files-download-all`);
|
|
/** @type {HTMLButtonElement} */
|
|
const eFileClearButton = document.querySelector(`button#${appletId}-files-clear`);
|
|
|
|
class ExcelFileData {
|
|
/** @type {File} */
|
|
originalFile;
|
|
|
|
/** @type {any} */
|
|
processedZipFile;
|
|
|
|
filesTotalCount = 0;
|
|
filesProcessedCount = 0;
|
|
passwordsRemoved = 0;
|
|
|
|
constructor(originalFile) {
|
|
this.originalFile = originalFile;
|
|
}
|
|
|
|
/** @returns {string} */
|
|
getOutputName() {
|
|
let outputZipExtension = "." + this.originalFile.name.split(".").pop();
|
|
let outputZipFilename = this.originalFile.name.substring(0, this.originalFile.name.length - outputZipExtension.length);
|
|
return outputZipFilename + "_no-password" + outputZipExtension;
|
|
}
|
|
}
|
|
|
|
/** @type {ExcelFileData[]} */
|
|
let rawWorksheetFiles = [];
|
|
|
|
function onFileAddedToDom() {
|
|
eResultEmptyText.hidden = true;
|
|
eResultPopulatedText.hidden = false;
|
|
}
|
|
|
|
function onFilesRemovedFromDom() {
|
|
eResultEmptyText.hidden = false;
|
|
eResultPopulatedText.hidden = true;
|
|
}
|
|
|
|
/**
|
|
* @param excelFile {ExcelFileData}
|
|
*/
|
|
function onFileHavingPasswordRemoved(excelFile) {
|
|
console.debug(`Removed ${excelFile.passwordsRemoved} password(s) from '${excelFile.originalFile.name}'`);
|
|
|
|
cloneTemplate(
|
|
eSuccessTemplate,
|
|
{
|
|
"tmpl-success-filename": excelFile.originalFile.name,
|
|
"tmpl-success-password-count": excelFile.passwordsRemoved
|
|
},
|
|
true
|
|
).then(eFragment => {
|
|
// Doesn't work on BS4 and RAW, it returns " "...
|
|
//const eFileOutput = eFragment.firstChild;
|
|
|
|
const eFileOutput = eFragment.querySelector("*");
|
|
eFileOutput.addEventListener("click", function() {
|
|
downloadProcessedFile(excelFile);
|
|
});
|
|
eResultContainer.appendChild(eFileOutput);
|
|
|
|
onFileAddedToDom();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param excelFile {ExcelFileData}
|
|
*/
|
|
function onFileWithoutPasswords(excelFile) {
|
|
console.warn(`No password(s) were found in ${excelFile.originalFile.name} !`);
|
|
|
|
cloneTemplate(
|
|
eWarningTemplate,
|
|
{
|
|
"tmpl-warning-filename": excelFile.originalFile.name,
|
|
"tmpl-warning-message-extension": "",
|
|
//"tmpl-warning-message-no-password": "",
|
|
},
|
|
true
|
|
).then(eFragment => {
|
|
console.debug(eFragment);
|
|
eResultContainer.appendChild(eFragment);
|
|
onFileAddedToDom();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param excelFile {ExcelFileData}
|
|
*/
|
|
function onFileWithInvalidExtension(excelFile) {
|
|
console.warn(`The given file '${excelFile.originalFile.name}' doesn't have an Excel extension`);
|
|
|
|
cloneTemplate(
|
|
eWarningTemplate,
|
|
{
|
|
"tmpl-warning-filename": excelFile.originalFile.name,
|
|
//"tmpl-warning-message-extension": "",
|
|
"tmpl-warning-message-no-password": "",
|
|
},
|
|
true
|
|
).then(eFragment => {
|
|
console.debug(eFragment);
|
|
eResultContainer.appendChild(eFragment);
|
|
onFileAddedToDom();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param excelFile {ExcelFileData}
|
|
* @param error {any}
|
|
*/
|
|
function onExtractionFailure(excelFile, error) {
|
|
console.error("Failed to extract the content of the file in the browser ! ("+error.message+")");
|
|
|
|
cloneTemplate(
|
|
eErrorTemplate,
|
|
{
|
|
"tmpl-error-filename": excelFile.originalFile.name,
|
|
//"tmpl-error-message-jszip": ""
|
|
},
|
|
true
|
|
).then(eFragment => {
|
|
console.debug(eFragment);
|
|
eResultContainer.appendChild(eFragment);
|
|
onFileAddedToDom();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param excelFile {ExcelFileData}
|
|
*/
|
|
function downloadProcessedFile(excelFile) {
|
|
excelFile.processedZipFile.generateAsync({type:"base64"}).then(function(b64Data) {
|
|
const eLink = document.createElement('a');
|
|
eLink.download = excelFile.getOutputName();
|
|
eLink.href = 'data:application/zip;base64,' + b64Data;
|
|
eLink.click();
|
|
}, function(err) {
|
|
console.error(err);
|
|
});
|
|
}
|
|
|
|
window.onload = function () {
|
|
eFileDownloadAllButton.addEventListener("click", function() {
|
|
document.querySelectorAll(".epr-download-all-click").forEach(eResultLine => {
|
|
console.debug(eResultLine);
|
|
eResultLine.click();
|
|
});
|
|
});
|
|
|
|
eFileClearButton.addEventListener("click", function() {
|
|
while (eResultContainer.firstChild) {
|
|
eResultContainer.firstChild.remove();
|
|
}
|
|
onFilesRemovedFromDom();
|
|
rawWorksheetFiles = [];
|
|
eFileInputClearButton.click();
|
|
});
|
|
|
|
eFileInput.addEventListener('change', function(e) {
|
|
rawWorksheetFiles = [];
|
|
|
|
for (let i = 0; i < e.target.files.length; i++) {
|
|
console.debug(e.target.files[i]);
|
|
rawWorksheetFiles.push(new ExcelFileData(e.target.files[i]));
|
|
}
|
|
|
|
rawWorksheetFiles.forEach(excelFile => {
|
|
if(!isExcelExtension(excelFile.originalFile.name)) {
|
|
onFileWithInvalidExtension(excelFile);
|
|
return;
|
|
}
|
|
|
|
JSZip.loadAsync(excelFile.originalFile).then(function(zip) {
|
|
console.group(`JSZip - ${excelFile.originalFile.name}`);
|
|
|
|
excelFile.processedZipFile = new JSZip();
|
|
excelFile.filesTotalCount = 0;
|
|
excelFile.filesProcessedCount = 0;
|
|
excelFile.passwordsRemoved = 0;
|
|
|
|
for(const[fileKey, fileValue] of Object.entries(zip.files)) {
|
|
excelFile.filesTotalCount++;
|
|
|
|
if(fileKey.match(excelWorksheetRegex)) {
|
|
console.debug("Checking: "+fileKey);
|
|
|
|
fileValue.async("string").then(function(fileText) {
|
|
console.group(`JSZip - ${excelFile.originalFile.name} - ${fileKey}`);
|
|
|
|
let startIndex = fileText.indexOf('<sheetProtection ');
|
|
|
|
if(startIndex === -1) {
|
|
// No password found.
|
|
excelFile.processedZipFile.file(fileKey, fileText);
|
|
console.debug("Analysed: "+fileKey);
|
|
} else {
|
|
// Removing the password.
|
|
let endIndex = fileText.indexOf('/>', startIndex) + 2;
|
|
fileText = fileText.replace(fileText.substr(startIndex, endIndex-startIndex), "");
|
|
excelFile.processedZipFile.file(fileKey, fileText);
|
|
console.debug("Processed: "+fileKey);
|
|
excelFile.passwordsRemoved++;
|
|
}
|
|
|
|
excelFile.filesProcessedCount++;
|
|
|
|
console.groupEnd();
|
|
});
|
|
} else {
|
|
// Other files.
|
|
console.debug("Ignoring: "+fileKey);
|
|
fileValue.async("string").then(function(fileText) {
|
|
console.group(`JSZip - ${excelFile.originalFile.name} - ${fileKey}`);
|
|
console.debug(`Copying as-is`);
|
|
|
|
excelFile.processedZipFile.file(fileKey, fileText);
|
|
excelFile.filesProcessedCount++;
|
|
|
|
console.groupEnd();
|
|
});
|
|
}
|
|
}
|
|
|
|
console.debug("Waiting for all the files to be processed !");
|
|
|
|
function waitFilesBeingProcessed() {
|
|
console.debug("Processed "+excelFile.filesProcessedCount+" file(s) out of "+excelFile.filesTotalCount);
|
|
|
|
if(excelFile.filesTotalCount !== excelFile.filesProcessedCount) {
|
|
setTimeout(waitFilesBeingProcessed, 50);
|
|
} else {
|
|
console.debug("Done !");
|
|
if(excelFile.passwordsRemoved > 0) {
|
|
onFileHavingPasswordRemoved(excelFile);
|
|
} else {
|
|
onFileWithoutPasswords(excelFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
setTimeout(waitFilesBeingProcessed, 50);
|
|
|
|
console.groupEnd();
|
|
}, function (e) {
|
|
onExtractionFailure(excelFile, e);
|
|
});
|
|
});
|
|
|
|
});
|
|
}
|
|
}
|