From 1fc9a9cd49173dcd1a708f0f8684990b74261299 Mon Sep 17 00:00:00 2001 From: Theo Barfoot <theo.barfoot@gmail.com> Date: Tue, 23 Apr 2024 10:19:05 +0000 Subject: [PATCH] issue #8 --- index.html | 584 +++++++++++++++++++++++++++-------------------------- 1 file changed, 298 insertions(+), 286 deletions(-) diff --git a/index.html b/index.html index 65505e8..a482a95 100644 --- a/index.html +++ b/index.html @@ -1,286 +1,298 @@ -<!DOCTYPE html> -<html> -<head> - <meta charset="UTF-8"> - <meta name="referrer" content="no-referrer" /> - <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src-elem 'self' 'unsafe-inline'; script-src 'self' https://cdnjs.cloudflare.com 'unsafe-inline' 'unsafe-eval'; img-src 'self'" > - <meta http-equiv="Permissions-Policy" content="accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(), gamepad=(), speaker-selection=(), conversion-measurement=(), focus-without-user-activation=(), hid=(), idle-detection=(), interest-cohort=(), serial=(), sync-script=(), trust-token-redemption=(), unload=(), window-placement=(), vertical-scroll=()" > - <title>Compute SHA-1 from CSV in browser</title> - <link rel="icon" type="image/x-icon" href="favicon.png"> - <style> - #options { - display: none; - } - table, th, td { - border: 1px solid black; - border-collapse: collapse; - } - #preview { - display: none; - } - #loading { - display: none; - } - #hashing { - display: none; - } - </style> -</head> -<body> - <h1>Choose a CSV file</h1> - <form id="myForm"> - <input type="file" id="csvFile" accept=".csv" /> - <br /> - <input type="submit" value="Process" /> - </form> - <div id="loading"> - <p>Loading data...</p> - </div> - <div id="hashing"> - <p>Hashing data...</p> - </div> - <div id="options"> - <h1>Field options</h1> - <div id="columnBoxes"></div> - </div> - <div id="preview"> - <h1>Data preview</h1> - <p id="previewLbl">Showing 0 of 0</p> - <table id="previewTable"> - <thead> - <tr id="previewHeadRow"></tr> - </thead> - <tbody id="previewTableBody"></tbody> - </table> - </div> - <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js" integrity="sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> - <script> - async function sha1(str) { - const enc = new TextEncoder(); - const hash = await crypto.subtle.digest('SHA-1', enc.encode(str)); - return Array.from(new Uint8Array(hash)) - .map(v => v.toString(16).padStart(2, '0')) - .join('').slice(0, 10); - } - </script> - <script> - function download(dataurl, filename) { - const link = document.createElement("a"); - link.href = dataurl; - link.download = filename; - link.click(); - } - - function setOptionsVisible(visible) { - document.getElementById("options").style.display = (visible ? "block" : "none"); - } - - function setPreviewVisible(visible) { - document.getElementById("preview").style.display = (visible ? "block" : "none"); - } - - function setLoadingVisible(visible) { - document.getElementById("loading").style.display = (visible ? "block" : "none"); - } - - function setHashingVisible(visible) { - document.getElementById("hashing").style.display = (visible ? "block" : "none"); - } - - function clearTable() { - const table = document.getElementById("previewTable"); - table.tBodies[0].innerHTML = ""; - const headerRow = document.getElementById("previewHeadRow"); - headerRow.innerHTML = ""; - document.getElementById("columnBoxes").innerHTML = ""; - } - </script> - <script> - const myForm = document.getElementById("myForm"); - const csvFile = document.getElementById("csvFile"); - const table = document.getElementById("previewTable"); - - const hashColour = "#a3ffff" - const excludeColour = "#ffa6a6"; - const stripeWidth = 6; - - myForm.addEventListener("submit", function (e) { - e.preventDefault(); - const input = csvFile.files[0]; - const reader = new FileReader(); - - // 👇 executed when a file is loaded - reader.onload = async function (e) { - // 👇 get the text from CSV file - const text = e.target.result; - - // 👇 parse it using D3.js - const data = d3.csvParse(text); - const dataWithHash = d3.csvParse(text); - - // Find what to do with each column - const toHash = []; - const toExclude = []; - data.columns.forEach(function(column) { - const value = document.getElementById("select_" + column).value; - switch (value) { - case "Hash and keep": - toHash.push(column); - break; - case "Hash and exclude": - toHash.push(column); - toExclude.push(column); - break; - case "Exclude": - toExclude.push(column); - break; - } - }); - console.log(toHash); - console.log(toExclude); - - for (let i = 0; i < data.length; i++) { - // Concatenate strings for hashing - let hashKey = ""; - toHash.forEach(function(column) { - hashKey += data[i][column]; - }) - // Hash - console.log("Hashing: " + hashKey); - let hash = await sha1(hashKey); - console.log("SHA-1: " + hash); - - data[i]["Hash"] = hash; - dataWithHash[i]["Hash"] = hash; - - // Delete fields in toExclude - toExclude.forEach(function(column) { - delete data[i][column]; - }) - } - - // console.log(data); - // console.log(dataWithHash); - - let csvContent = "data:text/csv;charset=utf-8," + encodeURI(d3.csvFormat(data)); - let originalCsvWithHash = "data:text/csv;charset=utf-8," + encodeURI(d3.csvFormat(dataWithHash)); - download(csvContent, "unidentifiable.csv"); - download(originalCsvWithHash, "original_with_hash.csv"); - setHashingVisible(false); - }; - - setHashingVisible(true); - reader.readAsText(input); - }); - - function updateSelect(e) { - const select = e.target; - let name = select.id.slice(7); - // Update colour of table column - const numCells = table.rows.length; - let value = select.value.toLowerCase(); - let toHash = value.includes("hash"); - let toExclude = value.includes("exclude"); - for (let i = 0; i < numCells; i++) { - let id = `previewCell_${name}_${i}`; - let cell = document.getElementById(id); - let colour1 = toHash ? hashColour : "transparent"; - let colour2 = toExclude ? excludeColour : "transparent"; - cell.style.backgroundImage = `repeating-linear-gradient(58deg, ${colour1}, ${colour1} ${stripeWidth}px, ${colour2} ${stripeWidth}px, ${colour2} ${stripeWidth * 2}px)`; - } - } - - csvFile.addEventListener("change", function (e) { - e.preventDefault(); - const input = csvFile.files[0]; - const selectsParent = document.getElementById("columnBoxes"); - - clearTable(); - - if (input == null) { - selectsParent.innerHTML = ""; - setOptionsVisible(false); - setPreviewVisible(false); - return; - } - - const reader = new FileReader(); - - // 👇 executed when a file is loaded - reader.onload = async function (e) { - // 👇 get the text from CSV file - const text = e.target.result; - - // 👇 parse it using D3.js - console.log("Parsing started"); - const data = d3.csvParse(text); - console.log("Parsing done"); - - // Get object for preview table - const previewTableBody = document.getElementById("previewTableBody"); - - // Header rows for preview - const headerRow = document.getElementById("previewHeadRow"); - for (let i = 0; i < data.columns.length; i++) { - let cell = headerRow.insertCell(i); - let column = data.columns[i]; - cell.id = `previewCell_${column}_0`; - cell.innerHTML = column; - } - - // Add rows to preview table - const toShow = Math.min(data.length, 10); - for (let i = 0; i < toShow; i++) { - const row = previewTableBody.insertRow(i); - for (let j = 0; j < data.columns.length; j++) { - let content = data[i][data.columns[j]]; - if (content.length > 25) { - content = content.slice(0, 22).trim() + "..."; - } - let cell = row.insertCell(j); - cell.id = `previewCell_${data.columns[j]}_${i + 1}`; - cell.innerHTML = content; - } - } - - // Add drop-down for each field - const options = ["Keep", "Exclude", "Hash", "Hash and exclude"]; - data.columns.forEach(function(name) { - // Label - const label = document.createElement("label"); - const id = "select_" + name; - label.for = id; - label.textContent = name + ": "; - selectsParent.appendChild(label); - // Select - const select = document.createElement("select"); - select.id = id; - select.addEventListener("change", updateSelect) - selectsParent.appendChild(select); - options.forEach(function(optionName) { - const option = document.createElement("option"); - option.value = optionName; - option.text = optionName; - select.appendChild(option); - }); - // Line break - selectsParent.appendChild(document.createElement("br")); - }); - - // Show size of dataset - document.getElementById("previewLbl").innerHTML = "Showing " + toShow + " of " + data.length; - - // Show options - setOptionsVisible(true); - setPreviewVisible(true); - setLoadingVisible(false); - }; - - // 👇 load the input file to the reader - setLoadingVisible(true); - console.log("Starting file reading"); - reader.readAsText(input); - }); - </script> -</body> -</html> +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <meta name="referrer" content="no-referrer" /> + <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src-elem 'self' 'unsafe-inline'; script-src 'self' https://cdnjs.cloudflare.com 'unsafe-inline' 'unsafe-eval'; img-src 'self'" > + <meta http-equiv="Permissions-Policy" content="accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(), gamepad=(), speaker-selection=(), conversion-measurement=(), focus-without-user-activation=(), hid=(), idle-detection=(), interest-cohort=(), serial=(), sync-script=(), trust-token-redemption=(), unload=(), window-placement=(), vertical-scroll=()" > + <title>Compute SHA-1 from CSV in browser</title> + <link rel="icon" type="image/x-icon" href="favicon.png"> + <style> + #options, #preview, #loading, #hashing { + display: none; + } + table, th, td { + border: 1px solid black; + border-collapse: collapse; + } + footer { + margin-top: 20px; + text-align: center; + } + #privacyNotice { + color: red; + font-weight: bold; + margin-bottom: 10px; + } + + </style> +</head> +<body> + <div id="privacyNotice"> + No data from the CSV file leaves your device. All processing is done locally. + </div> + <h1>Choose a CSV file</h1> + <form id="myForm"> + <input type="file" id="csvFile" accept=".csv" /> + <br /> + <input type="submit" value="Process" /> + </form> + <div id="loading"> + <p>Loading data...</p> + </div> + <div id="hashing"> + <p>Hashing data...</p> + </div> + <div id="options"> + <h1>Field options</h1> + <div id="columnBoxes"></div> + </div> + <div id="preview"> + <h1>Data preview</h1> + <p id="previewLbl">Showing 0 of 0</p> + <table id="previewTable"> + <thead> + <tr id="previewHeadRow"></tr> + </thead> + <tbody id="previewTableBody"></tbody> + </table> + </div> + <footer> + <p> + + <a href="https://github.com/cai4cai/sha1inbrowser/blob/main/README.md">Usage Notes</a> | + <a href="https://cai4cai.ml/terms/">Terms of Service</a> | + <a href="https://cai4cai.ml/privacy/">Privacy Policy</a> | + <a href="https://github.com/cai4cai/sha1inbrowser">View Source on GitHub</a> + </p> + <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js" integrity="sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> + <script> + async function sha1(str) { + const enc = new TextEncoder(); + const hash = await crypto.subtle.digest('SHA-1', enc.encode(str)); + return Array.from(new Uint8Array(hash)) + .map(v => v.toString(16).padStart(2, '0')) + .join('').slice(0, 10); + } + </script> + <script> + function download(dataurl, filename) { + const link = document.createElement("a"); + link.href = dataurl; + link.download = filename; + link.click(); + } + + function setOptionsVisible(visible) { + document.getElementById("options").style.display = (visible ? "block" : "none"); + } + + function setPreviewVisible(visible) { + document.getElementById("preview").style.display = (visible ? "block" : "none"); + } + + function setLoadingVisible(visible) { + document.getElementById("loading").style.display = (visible ? "block" : "none"); + } + + function setHashingVisible(visible) { + document.getElementById("hashing").style.display = (visible ? "block" : "none"); + } + + function clearTable() { + const table = document.getElementById("previewTable"); + table.tBodies[0].innerHTML = ""; + const headerRow = document.getElementById("previewHeadRow"); + headerRow.innerHTML = ""; + document.getElementById("columnBoxes").innerHTML = ""; + } + </script> + <script> + const myForm = document.getElementById("myForm"); + const csvFile = document.getElementById("csvFile"); + const table = document.getElementById("previewTable"); + + const hashColour = "#a3ffff" + const excludeColour = "#ffa6a6"; + const stripeWidth = 6; + + myForm.addEventListener("submit", function (e) { + e.preventDefault(); + const input = csvFile.files[0]; + const reader = new FileReader(); + + // 👇 executed when a file is loaded + reader.onload = async function (e) { + // 👇 get the text from CSV file + const text = e.target.result; + + // 👇 parse it using D3.js + const data = d3.csvParse(text); + const dataWithHash = d3.csvParse(text); + + // Find what to do with each column + const toHash = []; + const toExclude = []; + data.columns.forEach(function(column) { + const value = document.getElementById("select_" + column).value; + switch (value) { + case "Hash and keep": + toHash.push(column); + break; + case "Hash and exclude": + toHash.push(column); + toExclude.push(column); + break; + case "Exclude": + toExclude.push(column); + break; + } + }); + console.log(toHash); + console.log(toExclude); + + for (let i = 0; i < data.length; i++) { + // Concatenate strings for hashing + let hashKey = ""; + toHash.forEach(function(column) { + hashKey += data[i][column]; + }) + // Hash + console.log("Hashing: " + hashKey); + let hash = await sha1(hashKey); + console.log("SHA-1: " + hash); + + data[i]["Hash"] = hash; + dataWithHash[i]["Hash"] = hash; + + // Delete fields in toExclude + toExclude.forEach(function(column) { + delete data[i][column]; + }) + } + + // console.log(data); + // console.log(dataWithHash); + + let csvContent = "data:text/csv;charset=utf-8," + encodeURI(d3.csvFormat(data)); + let originalCsvWithHash = "data:text/csv;charset=utf-8," + encodeURI(d3.csvFormat(dataWithHash)); + download(csvContent, "unidentifiable.csv"); + download(originalCsvWithHash, "original_with_hash.csv"); + setHashingVisible(false); + }; + + setHashingVisible(true); + reader.readAsText(input); + }); + + function updateSelect(e) { + const select = e.target; + let name = select.id.slice(7); + // Update colour of table column + const numCells = table.rows.length; + let value = select.value.toLowerCase(); + let toHash = value.includes("hash"); + let toExclude = value.includes("exclude"); + for (let i = 0; i < numCells; i++) { + let id = `previewCell_${name}_${i}`; + let cell = document.getElementById(id); + let colour1 = toHash ? hashColour : "transparent"; + let colour2 = toExclude ? excludeColour : "transparent"; + cell.style.backgroundImage = `repeating-linear-gradient(58deg, ${colour1}, ${colour1} ${stripeWidth}px, ${colour2} ${stripeWidth}px, ${colour2} ${stripeWidth * 2}px)`; + } + } + + csvFile.addEventListener("change", function (e) { + e.preventDefault(); + const input = csvFile.files[0]; + const selectsParent = document.getElementById("columnBoxes"); + + clearTable(); + + if (input == null) { + selectsParent.innerHTML = ""; + setOptionsVisible(false); + setPreviewVisible(false); + return; + } + + const reader = new FileReader(); + + // 👇 executed when a file is loaded + reader.onload = async function (e) { + // 👇 get the text from CSV file + const text = e.target.result; + + // 👇 parse it using D3.js + console.log("Parsing started"); + const data = d3.csvParse(text); + console.log("Parsing done"); + + // Get object for preview table + const previewTableBody = document.getElementById("previewTableBody"); + + // Header rows for preview + const headerRow = document.getElementById("previewHeadRow"); + for (let i = 0; i < data.columns.length; i++) { + let cell = headerRow.insertCell(i); + let column = data.columns[i]; + cell.id = `previewCell_${column}_0`; + cell.innerHTML = column; + } + + // Add rows to preview table + const toShow = Math.min(data.length, 10); + for (let i = 0; i < toShow; i++) { + const row = previewTableBody.insertRow(i); + for (let j = 0; j < data.columns.length; j++) { + let content = data[i][data.columns[j]]; + if (content.length > 25) { + content = content.slice(0, 22).trim() + "..."; + } + let cell = row.insertCell(j); + cell.id = `previewCell_${data.columns[j]}_${i + 1}`; + cell.innerHTML = content; + } + } + + // Add drop-down for each field + const options = ["Keep", "Exclude", "Hash", "Hash and exclude"]; + data.columns.forEach(function(name) { + // Label + const label = document.createElement("label"); + const id = "select_" + name; + label.for = id; + label.textContent = name + ": "; + selectsParent.appendChild(label); + // Select + const select = document.createElement("select"); + select.id = id; + select.addEventListener("change", updateSelect) + selectsParent.appendChild(select); + options.forEach(function(optionName) { + const option = document.createElement("option"); + option.value = optionName; + option.text = optionName; + select.appendChild(option); + }); + // Line break + selectsParent.appendChild(document.createElement("br")); + }); + + // Show size of dataset + document.getElementById("previewLbl").innerHTML = "Showing " + toShow + " of " + data.length; + + // Show options + setOptionsVisible(true); + setPreviewVisible(true); + setLoadingVisible(false); + }; + + // 👇 load the input file to the reader + setLoadingVisible(true); + console.log("Starting file reading"); + reader.readAsText(input); + }); + </script> +</body> +</html>