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>