Files
phorge.fr/server.js
l-nmch f45a108808
All checks were successful
Docker Build and Push for Main Branch / docker (push) Successful in 11m31s
chore(core): Added bare TCP access
2026-05-11 15:50:11 +02:00

149 lines
5.2 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const net = require('net');
const { handleCommand } = require('./commands');
const asciiLogo = ` + **==+= ===
===+ %========= +====== ++* =*==+ =====+=+=== ++====*+
===+ === +=== ============ ==========* ==========- ============
===+ %=== +=== ===* ==== ====+ === +=== %=== +===
===# %=== ==== ===* ===# ===+ *==# === === ===*
%=== %=== ==== #=== ==== === ==+ === %==+*********====
===# %=== +=== -==+ *=== ==+ +=== *===# *================
===+ %=== ====* #=== ==== ==+ ========+ %==+
====+ %=== +===+ ===* ===# ==+ === ===
*=============== *==== ==== ==+ ===+ *===+ ===*
=*======*+ ============# ==+ ============+ ============%
%=== =+====++ **# ===+= ===++==== =*====++
%=== === ===
%=== ===+ ====
%=== ===============
*======= `;
const contentTypes = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.ico': 'image/x-icon',
'.json': 'application/json; charset=utf-8',
};
const buildPath = (requestedPath) => {
let safePath = requestedPath.split('?')[0].split('#')[0];
safePath = decodeURIComponent(safePath);
safePath = safePath.replace(/^\//, '');
if (!safePath || safePath === '') {
return path.join(__dirname, 'index.html');
}
const candidate = path.join(__dirname, safePath);
if (!candidate.startsWith(__dirname)) {
return null;
}
return candidate;
};
const isHttpRequest = (chunk) => {
return /^(GET|POST|HEAD|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\s+/i.test(chunk);
};
const sendHttpResponse = (socket, statusCode, statusText, headers, body) => {
const head = [`HTTP/1.1 ${statusCode} ${statusText}`];
for (const [key, value] of Object.entries(headers)) {
head.push(`${key}: ${value}`);
}
head.push('Connection: close');
head.push('');
if (body) {
socket.end(head.join('\r\n') + '\r\n' + body);
} else {
socket.end(head.join('\r\n') + '\r\n');
}
};
const handleHttp = (socket, requestData) => {
const [requestLine] = requestData.split('\r\n');
const [method, requestPath] = requestLine.split(' ');
const filePath = buildPath(requestPath);
if (!filePath) {
return sendHttpResponse(socket, 400, 'Bad Request', { 'Content-Type': 'text/plain; charset=utf-8' }, '400 Bad Request');
}
fs.stat(filePath, (err, stats) => {
if (err || !stats.isFile()) {
return sendHttpResponse(socket, 404, 'Not Found', { 'Content-Type': 'text/plain; charset=utf-8' }, '404 Not Found');
}
fs.readFile(filePath, (readErr, data) => {
if (readErr) {
return sendHttpResponse(socket, 500, 'Internal Server Error', { 'Content-Type': 'text/plain; charset=utf-8' }, '500 Internal Server Error');
}
const ext = path.extname(filePath).toLowerCase();
const contentType = contentTypes[ext] || 'application/octet-stream';
sendHttpResponse(socket, 200, 'OK', { 'Content-Type': contentType, 'Content-Length': Buffer.byteLength(data) }, data);
});
});
};
const createTerminalSession = (socket, initialData) => {
socket.write(asciiLogo + '\r\n\r\n');
socket.write('Open-Source & Community first Cloud Provider (/help for assistance)\r\n');
socket.write('phorge> ');
let buffer = '';
if (initialData) {
buffer += initialData;
}
const flushLines = () => {
let index;
while ((index = buffer.indexOf('\n')) !== -1) {
let line = buffer.slice(0, index).replace(/\r$/, '');
buffer = buffer.slice(index + 1);
handleCommand(socket, line);
}
};
socket.on('data', (chunk) => {
buffer += chunk.toString('utf8');
flushLines();
});
socket.on('error', () => {
socket.destroy();
});
};
const server = net.createServer((socket) => {
socket.setEncoding('utf8');
let hasHandled = false;
const onFirstData = (chunk) => {
if (hasHandled) return;
hasHandled = true;
const data = chunk.toString('utf8');
if (isHttpRequest(data)) {
handleHttp(socket, data);
} else {
createTerminalSession(socket, data);
}
};
socket.once('data', onFirstData);
socket.on('error', () => {
socket.destroy();
});
});
const PORT = process.env.PORT || 80;
server.listen(PORT, () => {
console.log(`Phorge server listening on port ${PORT}`);
});