/** * This script redraws the default lighttpd directory listing to our custom styles. * * Since the person inserting the files has physical access to the box and we serve * their static sites directly from the USB, ***THIS SCRIPT DOES NOT ATTEMPT TO PREVENT * INJECTION ATTACKS***. We just pop filenames and the like right into innerHTML. */ const supported_extensions = ["apk", "deb", "dmg", "pdf", "exe", "jpg", "png", "pptx", "ppt", "mp3"]; const usbRoot = "usb-butter/"; const inferredBaseURL = window.location.pathname.split("/" + usbRoot)[0] + "/"; // Don't display these in the dirlisting; they get first-class treatment // on the homepage. const foldersToHide = ["appstore", "osm-map-files"]; const getFolderDivHTML = (directory_name, number_of_items, href) => { return `
directory
${directory_name}
${number_of_items} items
`; }; const getFileDivHTML = (file_name, size, date, href) => { icon = "ext-unknown.svg"; extension = file_name.split('.').pop(); if (supported_extensions.includes(extension)) { icon = "ext-" + extension + ".svg"; } // turn date from "2021-09-01 12:34:56" to "Sep 1, 2021" date = new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); return `
directory
${file_name}
${size} | ${date}
download
`; }; const getFolderItemCount = async (folder_href) => { async function populateSpan(response) { if (!response.ok) { console.error("Failed to fetch " + folder_href); return; } const text = await response.text(); const doc = new DOMParser().parseFromString(text, 'text/html'); const { files, folders } = extractDirectoryListing(doc); const items = files.length + folders.length; const span = document.getElementById(folder_href); span.textContent = items; } const response = fetch(folder_href).then(populateSpan); } // Function to extract directory listing information function extractDirectoryListing(doc) { // Get all rows in the table body const rows = doc.querySelectorAll('tbody tr'); // Initialize arrays to hold file and folder information const files = []; const folders = []; // Iterate over each row rows.forEach(row => { const nameCell = row.querySelector('td.n a'); const modifiedCell = row.querySelector('td.m'); const sizeCell = row.querySelector('td.s'); const typeCell = row.querySelector('td.t'); const name = nameCell.textContent; const lastModified = modifiedCell.textContent.trim(); const size = sizeCell.textContent.trim(); const type = typeCell.textContent.trim(); const href = nameCell.getAttribute('href'); // Determine if it's a file or folder based on the class of the row or type if (type === 'Directory') { if (name !== '..' && !foldersToHide.includes(name)) { folders.push({ name: name.replace('/', ''), // Remove the trailing slash lastModified: lastModified, size: size, type: type, href: href }); } } else { if (!name.startsWith('.')) { files.push({ name: name, lastModified: lastModified, size: size, type: type, href: href }); } } }); return { files, folders }; } function renderDirectory() { const { files, folders } = extractDirectoryListing(window.document); const listDiv = document.querySelector('div.list'); // Header with breadcrumbs const h2Path = document.querySelector('h2').textContent; const path = h2Path.replace('Index of ', '').replace(usbRoot, ''); const breadcrumbs = document.createElement("div"); breadcrumbs.classList.add('breadcrumbs'); let breadcrumbHTML = ` home Explore `; const pathSteps = path.split('/'); let thisDirsName = "Explore"; let pathToHere = usbRoot; pathSteps.forEach((step, index) => { if (step !== "") { pathToHere += step + '/'; thisDirsName = step; breadcrumbHTML += ` ${step}`; } }); breadcrumbs.innerHTML = breadcrumbHTML; listDiv.parentNode.insertBefore(breadcrumbs, listDiv); // hr const fullWithHR = document.createElement('hr'); fullWithHR.classList.add('full-width'); listDiv.parentNode.insertBefore(fullWithHR, listDiv); // Secondary header const secondaryHeader = document.createElement('div'); secondaryHeader.classList.add('breadcrumb-name'); secondaryHeader.innerHTML = ``; secondaryHeader.innerHTML += `${thisDirsName}`; listDiv.parentNode.insertBefore(secondaryHeader, listDiv); // Create a folder listing const folderListing = document.createElement("div"); folderListing.classList.add('folder-list'); if (folders.length > 0) { folderListing.innerHTML = '

Folders

'; } // for each folder, add a div to folderListing folders.forEach(folder => { const folderDiv = document.createElement('div'); folderDiv.classList.add('folder-row'); folderDiv.innerHTML = getFolderDivHTML(folder.name, "?", folder.href); folderListing.appendChild(folderDiv); getFolderItemCount(folder.href); }); // do the insertion listDiv.parentNode.insertBefore(folderListing, listDiv); if (folders.length > 0 && files.length > 0) { listDiv.parentNode.insertBefore(document.createElement('hr'), listDiv); } // then for files const fileListing = document.createElement("div"); fileListing.classList.add('file-list'); if (files.length > 0) { fileListing.innerHTML = '

Files

'; } // for each file, add a div to fileListing files.forEach(file => { const fileDiv = document.createElement('div'); fileDiv.classList.add('file-row'); fileDiv.innerHTML = getFileDivHTML(file.name, file.size, file.lastModified, file.href); fileListing.appendChild(fileDiv); }); // do the insertion listDiv.parentNode.insertBefore(fileListing, listDiv); } window.onload = function () { if (window.location.pathname.includes(usbRoot)) { renderDirectory(); } }