function draw_bytes(data) { const canvas = document.getElementById("blob"); const ctx = canvas.getContext("2d"); const colorScale = chroma.scale(['#4c6c81', '#9c2f2f']).mode('lch'); const numBlocks = Math.ceil(data.meta.sizeb / 1200); const blockSize = canvas.width / numBlocks; ctx.fillStyle = "#ddd"; ctx.fillRect(0, 0, canvas.width, canvas.height); // console.log("size: " + data.meta.sizeb); // console.log("numblocks: " + numBlocks); data.offsets.forEach((offset, idx) => { const start = Math.floor((offset.start / data.meta.sizeb) * numBlocks); const end = Math.ceil((offset.end / data.meta.sizeb) * numBlocks); // console.log(offset.d + ": " + start + ", " + end + " | " + offset.start + ", " + offset.end); ctx.fillStyle = colorScale(idx / data.offsets.length).alpha(0.2); ctx.fillRect(start * blockSize, 0, (end - start) * blockSize, canvas.height); }); canvas.addEventListener('mousemove', function(event) { const rect = canvas.getBoundingClientRect(); const mouseX = event.clientX - rect.left; const mouseY = event.clientY - rect.top; const tooltips = []; data.offsets.forEach((offset, idx) => { const start = Math.floor((offset.start / data.meta.sizeb) * numBlocks); const end = Math.ceil((offset.end / data.meta.sizeb) * numBlocks); const rectX = start * blockSize; const rectY = 0; const rectWidth = (end - start) * blockSize; const rectHeight = canvas.height; if (mouseX >= rectX && mouseX <= rectX + rectWidth && mouseY >= rectY && mouseY <= rectY + rectHeight) { const tooltipText = ` ${first_substr(offset.d)}`; const tooltipWidth = ctx.measureText(tooltipText).width + 20; const tooltipHeight = 30; let tooltipX = mouseX + 10; let tooltipY = mouseY - tooltipHeight - 10; if (tooltipX + tooltipWidth > canvas.width) { tooltipX = canvas.width - tooltipWidth; } if (tooltipY < 0) { tooltipY = 0; } // Check for overlapping tooltips for (let i = 0; i < tooltips.length; i++) { const otherTooltip = tooltips[i]; if (tooltipX < otherTooltip.x + otherTooltip.width && tooltipX + tooltipWidth > otherTooltip.x && tooltipY < otherTooltip.y + otherTooltip.height && tooltipY + tooltipHeight > otherTooltip.y) { // Overlapping, adjust the position tooltipX = otherTooltip.x + otherTooltip.width + 10; if (tooltipX + tooltipWidth > canvas.width) { tooltipX = mouseX - tooltipWidth - 10; } if (tooltipX < 0) { tooltipX = 0; } tooltipY = otherTooltip.y; } } tooltips.push({ x: tooltipX, y: tooltipY, width: tooltipWidth, height: tooltipHeight }); ctx.fillStyle = '#fff'; ctx.fillRect(tooltipX, tooltipY, tooltipWidth, tooltipHeight); ctx.fillStyle = '#000'; ctx.font = '.6em Arial'; ctx.fillText(tooltipText, tooltipX + 10, tooltipY + 20); // colored circle const circleSize = 10; ctx.fillStyle = colorScale(idx / data.offsets.length).alpha(0.2); ctx.beginPath(); ctx.arc(tooltipX + 15, tooltipY + 15, circleSize / 2, 0, 2 * Math.PI); ctx.fill(); // re-draw the rest ctx.fillStyle = colorScale(idx / data.offsets.length).alpha(0.2); ctx.fillRect(start * blockSize, 0, (end - start) * blockSize, canvas.height); } }); }); canvas.addEventListener('mouseout', function(event) { ctx.clearRect(0, 0, canvas.width, canvas.height); data.offsets.forEach((offset, idx) => { const start = Math.floor((offset.start / data.meta.sizeb) * numBlocks); const end = Math.ceil((offset.end / data.meta.sizeb) * numBlocks); ctx.fillStyle = colorScale(idx / data.offsets.length).alpha(0.2); ctx.fillRect(start * blockSize, 0, (end - start) * blockSize, canvas.height); }); }); } // function create_listing(listing) { // const ul = document.createElement('ul'); // ul.classList.add("tree-padding", "tree-vertical-lines", "tree-horizontal-lines", "tree-summaries", "tree-markers", "tree-buttons") // for (const key in listing) { // const value = listing[key]; // if (typeof value == 'object') { // const li = document.createElement('li'); // const summary = document.createElement('summary'); // summary.innerText = key; // li.appendChild(summary); // const nestedUl = create_listing(value); // li.appendChild(nestedUl); // ul.appendChild(li); // } else { // const li = document.createElement('li'); // const summary = document.createElement('summary'); // summary.classList.add('no-pico'); // summary.innerText = `${key} (${value.s})`; // li.appendChild(summary); // ul.appendChild(li); // } // } // return ul; // } function file_summaries(data) { const ul = document.createElement('ul'); for (const [key, val] of Object.entries(data.ql)) { const li = document.createElement('li'); li.textContent = `${val}`; ul.appendChild(li); } return ul; } function create_listing(listing) { const ul = document.createElement('ul'); for (const key in listing) { const value = listing[key]; if ('s' in value) { const li = document.createElement('li'); li.innerText = `${key} (${bytes_to_human(value.s)})`; ul.appendChild(li); } else { // (typeof value === 'object') { const li = document.createElement('li'); li.innerText = `${key}/`; const nestedUl = create_listing(value); li.appendChild(nestedUl); ul.appendChild(li); } } return ul; } function bytes_to_human(bytes) { num = parseInt(bytes); const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; let i =0; while (num >= 1024 && i < units.length - 1) { num /= 1024; i++; } return num.toFixed(2) + " " + units[i]; } function first_substr(description) { const index = description.indexOf(','); if (index === -1) { return description; } else { return description.substring(0, index); } } async function process_upload(file) { const formData = new FormData(); formData.append('file', file); const button = document.querySelector('button'); button.setAttribute('aria-busy', 'true'); button.textContent = `processing ${file.name}...`; const response = await fetch('api/upload_file', { method: 'POST', body: formData, timeout: 600000 // 10 minutes }); if (response.ok) { const data = await response.json(); const container = document.querySelector('.container'); container.innerHTML = `

${data.meta.name}

blob meta
input filename: ${data.meta.name}
process duration: ${data.meta.duration}
file size in bytes: ${data.meta.sizeb} (${bytes_to_human(data.meta.sizeb)})
files discovered: ${data.meta.files}
directories unpacked: ${data.meta.dirs}
signatures tried: ${data.meta.sig_quant}
sha1 hash: ${data.meta.sha1}
md5 hash: ${data.meta.md5}
binwalk extraction log
full file listing
raw json response (dev)
${JSON.stringify(data, null, 2)}
`; draw_bytes(data); const file_list = document.getElementById('file-list'); file_list.appendChild(create_listing(data.ls[Object.keys(data.ls)[0]])); const quick_look = document.getElementById('quick-look'); quick_look.appendChild(file_summaries(data)); } else { console.error('error uploading file'); } } function upload_file() { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.addEventListener('change', (event) => { const file = event.target.files[0]; process_upload(file); }); fileInput.click(); }