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)] %>