From 9d93ff78128c63c939cc895654cf305300024df6 Mon Sep 17 00:00:00 2001 From: oddbyte Date: Mon, 19 Jan 2026 02:15:26 -0500 Subject: [PATCH] yeet source viewer; replace with gitea --- index.js | 320 ------------------------------------- start_dev.sh | 7 + views/partials/funfact.ejs | 4 +- views/partials/navbar.ejs | 2 +- views/source.ejs | 79 --------- views/sourceviewer.ejs | 25 --- 6 files changed, 10 insertions(+), 427 deletions(-) create mode 100755 start_dev.sh delete mode 100755 views/source.ejs delete mode 100755 views/sourceviewer.ejs diff --git a/index.js b/index.js index eb18946..319da58 100755 --- a/index.js +++ b/index.js @@ -60,36 +60,6 @@ const loopHourly = (something, skipFirst = false) => { }, 3600000 - new Date().getTime() % 3600000); } -function sendSource(req, res, filePath) { - try { - // This will throw an error, which I catch, if the file isnt found - // Use lstatSync to have a sync read while also not going through symlinks (to prevent arbritrary reads via symlinks) - // Further restricted by my use of the --permission commandline flag - // I also added a manual check to block any file reads outside the base dir (/prod/portfolio as of the time of writing this) - const stat = fs.lstatSync(filePath); - - if (!stat.isFile()) { - // If it's not a file, error - res.status(403).send('
Not a file
'); - } else { - // If it's a file, send the content - const fileContent = fs.readFileSync(filePath, 'utf8'); - res.render('sourceviewer', { faviconb64, filePath, fileContent }); - } - } catch (err) { - // Legit why are you doing this - // If it's whitelisted, it's listed under the index page - // If it's not whitelisted, you cant access it anyway :P - res.status(404).send('File not found'); - } -} - -// -------------------------------------------------------------------------------------- -// - Below is black magic. I have no idea how it works. - -// - I have either stolen it from somewhere or completely forgotten how it works. - -// - If you know how it works please explain it to me PLEASE I BEG OF YOU HALP :AAAAAA: - -// -------------------------------------------------------------------------------------- - /** * Pulls all the pictures for the songs and stores them in a base64 key:value array where the key is the song id and the value is the thumbnail * @param {*} song @@ -128,246 +98,6 @@ async function updateSongs() { } } -/** - * @param {Array} pathPattern This thingy is the indexPaths below this function - * @returns {Array} an array called `results` - */ -function getDirectoryContents(pathPattern) { - const results = []; - - // Handle wildcards (including multiple wildcards like blog/*/*) - if (pathPattern.includes('*')) { - const segments = pathPattern.split('/').filter(s => s); - - /** - * Recursively resolves the pattern segments against the file system. - * @param {Array} remainingSegments - The parts of the path left to match (e.g., ['*']). - * @param {String} currentAbsPath - The absolute path on disk we are currently checking. - * @returns {Array} - Array of matched file/directory objects. - */ - function traverse(remainingSegments, currentAbsPath) { - // Base case: We have matched all segments. Return the node at this location. - if (remainingSegments.length === 0) { - return [createNode(currentAbsPath)]; - } - - const segment = remainingSegments[0]; - const rest = remainingSegments.slice(1); - - if (segment === '*') { - // Wildcard: Read current directory and recurse for all children - try { - const files = fs.readdirSync(currentAbsPath, { withFileTypes: true }); - const matches = []; - - files.forEach(file => { - const fullPath = path.join(currentAbsPath, file.name); - // Recurse with the remaining segments - const childMatches = traverse(rest, fullPath); - matches.push(...childMatches); - }); - - return matches; - } catch (err) { - console.error(`Error reading directory ${currentAbsPath}:`, err); - return []; - } - } else { - // Exact segment: Join path and recurse - const fullPath = path.join(currentAbsPath, segment); - return traverse(rest, fullPath); - } - } - - /** - * Creates the result object for a specific path. - * If it's a directory, it recursively fetches children. - */ - function createNode(fullPath) { - try { - const stat = fs.statSync(fullPath); - const relativePath = path.relative(baseDir, fullPath); - - if (stat.isDirectory()) { - // Recursively get children using the main function - // We append '/*' to get the immediate contents of this directory - const children = getDirectoryContents(`${relativePath}/*`); - return { - name: path.basename(fullPath), - path: relativePath, - type: 'directory', - children: children - }; - } else { - return { - name: path.basename(fullPath), - path: relativePath, - type: 'file' - }; - } - } catch (err) { - console.error(`Error reading path ${fullPath}:`, err); - return null; - } - } - - // Start traversal from the base directory - const nodes = traverse(segments, baseDir); - // Filter out any null results from errors - nodes.forEach(node => { - if (node) results.push(node); - }); - - } else { - // Handle exact paths - - // Thanks past me, for the extremely helpful comment above - // I now understand exactly what this does /s - - const fullPath = path.join(baseDir, pathPattern); - try { - const stat = fs.statSync(fullPath); - const relativePath = path.relative(baseDir, fullPath); - - if (stat.isDirectory()) { - const files = fs.readdirSync(fullPath, { withFileTypes: true }); - files.forEach(file => { - const childPath = path.join(fullPath, file.name); - const childRelativePath = path.relative(baseDir, childPath); - - if (file.isDirectory()) { - const children = getDirectoryContents(`${childRelativePath}/*`); - results.push({ - name: file.name, - path: childRelativePath, - type: 'directory', - children: children - }); - } else { - results.push({ - name: file.name, - path: childRelativePath, - type: 'file' - }); - } - }); - } else { - results.push({ - name: path.basename(fullPath), - path: relativePath, - type: 'file' - }); - } - } catch (err) { - console.error(`Error reading path ${fullPath}:`, err); - } - } - - return results; -} - -/** - * An array of the whitelisted directories and their contents - */ -let whitelistedFiles = []; - -/** - * Whitelisted paths for the source code (to prevent arbritrary reads and stuffs), - * Also referred to as `pathPattern` in whatever the hell `getDirectoryContents()` is - */ -const indexPaths = [ - 'blog/*', - 'blog/*/*', - 'public/*', - 'public/piskelfile/*', - 'views/*', - 'views/partials/*', - 'index.js' -]; - -/** - * directory tree object (built in the func buildDirectoryTree below) - */ -let directoryTree; - -/** - * sorted result - */ -let sortedResult; - -// Build directory tree -const buildDirectoryTree = (items) => { - const tree = {}; - items.forEach(item => { - const parts = item.path.split(path.sep).filter(part => part !== ''); - let currentLevel = tree; - for (let i = 0; i < parts.length; i++) { - const part = parts[i]; - if (!currentLevel[part]) { - currentLevel[part] = { - name: part, - path: parts.slice(0, i + 1).join(path.sep), - type: i === parts.length - 1 ? item.type : 'directory', - children: {} - }; - } - currentLevel = currentLevel[part].children; - } - if (item.type === 'file') { - currentLevel[parts[parts.length - 1]] = { - ...currentLevel[parts[parts.length - 1]], - type: 'file' - }; - } - }); - return tree; -}; - -// Convert the tree to an array for easier rendering -const treeToArray = (node) => { - const result = []; - Object.values(node).forEach(item => { - if (item.type === 'directory') { - const directoryItem = { - name: item.name, - path: item.path, - type: 'directory', - children: treeToArray(item.children) - }; - result.push(directoryItem); - } else { - result.push({ - name: item.name, - path: item.path, - type: 'file' - }); - } - }); - return result.sort((a, b) => { - if (a.type === 'directory' && b.type === 'file') return -1; - if (a.type === 'file' && b.type === 'directory') return 1; - return a.name.localeCompare(b.name); - }); -}; - -async function updateFileIndex() { - // Clear whitelisted files to prevent memory leak - whitelistedFiles = []; - - indexPaths.forEach(path => { - let contents = getDirectoryContents(path); - whitelistedFiles.push(...contents); - }); - - directoryTree = buildDirectoryTree(whitelistedFiles); - sortedResult = treeToArray(directoryTree); -} - -// ---------------------------------------------------------- -// - End black magic - -// - I am pretty sure I know what is going on below here :p - -// ---------------------------------------------------------- - var blog; // Setup the blog variable // Blog stuffs @@ -445,45 +175,6 @@ app.get('/art', (req, res) => { res.render('art', { faviconb64 }); }); -// Source code page -app.get('/source', (req, res) => { - res.render('source', { faviconb64, paths: sortedResult }); -}); - -// Route to serve indexed files -app.get(/^\/source\/.*/, (req, res) => { - const requestedPath = req.path.split('/source/')[1]; - - // Check if the requested file is whitelisted - const isIndexed = indexPaths.some(pattern => { - if (pattern.toString().includes("*")) { - return whitelistedFiles.some(pattern => { return requestedPath.startsWith(pattern.path) && !(requestedPath.split(pattern.path).some(pattern => {return pattern.includes("/");}));}); - } - return requestedPath === pattern; - }); - - if (!isIndexed) { - return res.status(403).send('
File not whitelisted
'); - } - - const filePath = path.join(baseDir, requestedPath); - - // Completely prevent ../ attacks (hopefully) - // Well I mean it wont prevent bind mounts but if you can bind mount I'm fucked anyways cause you're root - // Symlinks might (?) bypass this?? - // Hardlinks defo bypass this afaik. - // because i pass the --permission flag and only pass --allow-fs-read=/prod/portfolio/* - // you can't write via NJS at all - // nor read outside /prod/portfolio - // you'd need to break outta nodejs and start a native process - // and my filesystem permissions wont letchu write stuff anyway - if (!filePath.startsWith(baseDir)) { - return res.status(403).send('
File not whitelisted
'); - } - - sendSource(req, res, filePath); -}); - // Moosic page app.get('/music', (req, res) => { res.render('moosic', { faviconb64, playlistId, playlistSongs, thumbnails }); @@ -551,7 +242,6 @@ function printHelp() { rl.write("\n\n-------------------------------HELP-----------------------------------------\n"); rl.write("> help [?] - prints this message\n"); rl.write("> updateMusic [um] - runs updateSongs()\n"); - rl.write("> updateFileIndex [ufi] - runs updateFileIndex()\n"); rl.write("> eval [exec] - runs raw JS code, returns output (if any)\n"); rl.write("> quit [exit] - stops the server, and quits\n"); rl.write("-------------------------------HELP-----------------------------------------\n\n") @@ -581,10 +271,6 @@ async function customConsole() { case "updateMusic": updateSongs(); break; - case "ufi": - case "updateFileIndex": - updateFileIndex(); - break; case "exec": case "eval": // TODO: Fix this not actually using the correct context for whatever reason it just evals in an empty context @@ -623,9 +309,6 @@ async function main() { // Populate the blog index await generateBlogIndex(); - // Populate file index - await updateFileIndex(); - server = http.createServer(app); await (async () => { @@ -634,9 +317,6 @@ async function main() { // Start hourly loop to update playlist loopHourly(async () => await updateSongs()); - - // Start hourly loop to update file index - loopHourly(async () => await updateFileIndex()); }); })(); diff --git a/start_dev.sh b/start_dev.sh new file mode 100755 index 0000000..664c0b9 --- /dev/null +++ b/start_dev.sh @@ -0,0 +1,7 @@ +#!/bin/bash +echo '==== Cleaning up dead screens ====' +screen -wipe portfolio_devel +echo '==== Restarting Nginx ====' +rc-service nginx restart +echo '===== Starting server ====' +sudo -iu prod bash -c 'cd /development/website && node --permission --allow-fs-read=/development/website/* .' diff --git a/views/partials/funfact.ejs b/views/partials/funfact.ejs index d09cf95..03fd52d 100755 --- a/views/partials/funfact.ejs +++ b/views/partials/funfact.ejs @@ -3,12 +3,12 @@ 'Android was originally an operating system for cameras', '"password" is still one of the most commonly used passwords', 'People call me funny sometimes I guess', - 'Haiii <3', 'e^(i*pi) = -1', 'According to the leading fan theory, 1 + 1 might actually not be 11', 'xkcd.com is a thing, and it is funny most of the time', 'Apparently I am a hooman', 'This message changes every time you refresh the page', 'I wish my friends would talk to me more often', - 'I am a functional introvert' + 'I am a functional introvert', + 'Haiii <3' ]; %>Fun Fact: <%= facts[Math.floor(Math.random() * facts.length)] %>

diff --git a/views/partials/navbar.ejs b/views/partials/navbar.ejs index 185b58b..d719295 100755 --- a/views/partials/navbar.ejs +++ b/views/partials/navbar.ejs @@ -3,6 +3,6 @@ Home Art Playlist - Source code + Gitea diff --git a/views/source.ejs b/views/source.ejs deleted file mode 100755 index 35a8d13..0000000 --- a/views/source.ejs +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - Directory Tree - - - - -<%- include('partials/navbar') %> -
-
-
-
-

Directory Tree

-

(here is where all the raw files are)

- -
    - <% function renderTree(items, indentLevel = 0) { %> - <% items.forEach(item => { %> -
  • - <% if (item.type === 'directory') { %> - <%= item.name %> - <% if (item.children && item.children.length > 0) { %> -
      - <%= renderTree(item.children, indentLevel + 1) %> -
    - <% } %> - <% } else { %> - <%= item.name %> - <% } %> -
  • - <% }); %> - <% } %> - - <%= renderTree(paths) %> -
- -

Rendered Pages

-
-
-

these are the thingies that you see when you use my site like a normal person (in a browser, hopefully)

-

- / <-- Main Page
- /music <-- My Youtube Music playlist
- /source <-- You are here :P
- /art <-- My art ^.^
- /blog <-- My crappy blog
-

-
-
-

Files

-
-
-

- /favicon.ico <-- icon
- /robots.txt <-- tells some robots to go away -

-
-
-
-
-
- - diff --git a/views/sourceviewer.ejs b/views/sourceviewer.ejs deleted file mode 100755 index dfc4ce2..0000000 --- a/views/sourceviewer.ejs +++ /dev/null @@ -1,25 +0,0 @@ - - - - Oddbyte - - - - - - - - -

Source code for <%-filePath %>

-

Back

-
-
<%= fileContent %>
-
-

Back

- - -- 2.52.0