Skip to content

Commit

Permalink
Merge branch 'breck7:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
bardicreels authored Nov 10, 2024
2 parents 9f2463b + 25f3f48 commit d5084ff
Show file tree
Hide file tree
Showing 12 changed files with 2,199 additions and 53 deletions.
158 changes: 158 additions & 0 deletions ScriptRunner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
const { spawn } = require("child_process")
const fsp = require("fs").promises
const path = require("path")

// Map of file extensions to their interpreters
const interpreterMap = {
php: "php",
py: "python3",
rb: "ruby",
pl: "perl"
}

const TIMEOUT = 20000

class ScriptRunner {
constructor(hub) {
this.hub = hub
}

init() {
const { hub } = this
const { app, rootFolder, folderCache } = hub
const scriptExtensions = Object.keys(interpreterMap).join("|")
const scriptPattern = new RegExp(`.*\.(${scriptExtensions})$`)

// Handle both GET and POST requests
app.all(scriptPattern, async (req, res) => {
const folderName = hub.getFolderName(req)

// Check if folder exists
if (!folderCache[folderName]) return res.status(404).send("Folder not found")

const folderPath = path.join(rootFolder, folderName)
const filePath = path.join(folderPath, path.basename(req.path))

// Ensure the file path is within the folder (prevent directory traversal)
if (!filePath.startsWith(folderPath)) return res.status(403).send("Access denied")

const ext = path.extname(filePath).slice(1)
const interpreter = interpreterMap[ext]

try {
// Check if file exists
const fileExists = await fsp
.access(filePath)
.then(() => true)
.catch(() => false)

if (!fileExists) return res.status(404).send("Script not found")

// Prepare environment variables with request data
const env = {
...process.env,
FOLDER_NAME: folderName,
REQUEST_METHOD: req.method,
QUERY_STRING: new URLSearchParams(req.query).toString(),
CONTENT_TYPE: req.get("content-type") || "",
CONTENT_LENGTH: req.get("content-length") || "0",
HTTP_HOST: req.get("host") || "",
HTTP_USER_AGENT: req.get("user-agent") || "",
REMOTE_ADDR: req.ip || req.connection.remoteAddress,
SCRIPT_FILENAME: filePath,
SCRIPT_NAME: req.path,
DOCUMENT_ROOT: folderPath,
// Add more CGI-like environment variables
PATH_INFO: filePath,
SERVER_NAME: req.hostname,
SERVER_PORT: req.protocol === "https" ? "443" : "80",
SERVER_PROTOCOL: "HTTP/" + req.httpVersion,
SERVER_SOFTWARE: "ScrollHub"
}

// Add all HTTP headers as environment variables
Object.entries(req.headers).forEach(([key, value]) => {
env["HTTP_" + key.toUpperCase().replace("-", "_")] = value
})

// Spawn the interpreter process
const spawnedProcess = spawn(interpreter, [filePath], {
env,
cwd: folderPath, // Set working directory to the folder
stdio: ["pipe", "pipe", "pipe"]
})

// Send POST data to script's stdin if present
if (req.method === "POST") {
const postData = req.body
if (typeof postData === "string") {
spawnedProcess.stdin.write(postData)
} else if (Buffer.isBuffer(postData)) {
spawnedProcess.stdin.write(postData)
} else {
spawnedProcess.stdin.write(JSON.stringify(postData))
}
spawnedProcess.stdin.end()
}

let output = ""
let errorOutput = ""

spawnedProcess.stdout.on("data", data => {
output += data.toString()
})

spawnedProcess.stderr.on("data", data => {
errorOutput += data.toString()
})

// Set timeout for script execution (e.g., 30 seconds)
const timeout = setTimeout(() => {
spawnedProcess.kill()
res.status(504).send("Script execution timed out")
}, TIMEOUT)

spawnedProcess.on("close", code => {
clearTimeout(timeout)

if (code === 0) {
// Parse output for headers and body
const parts = output.split("\r\n\r\n")
if (parts.length > 1) {
// Script provided headers
const headers = parts[0].split("\r\n")
const body = parts.slice(1).join("\r\n\r\n")

headers.forEach(header => {
const [name, ...value] = header.split(":")
if (name && value) {
res.setHeader(name.trim(), value.join(":").trim())
}
})
res.send(body)
} else {
// No headers provided, send as plain text
res.setHeader("Content-Type", "text/plain")
res.send(output)
}
} else {
console.error(`Script execution error in ${filePath} (${code}):`, errorOutput)
res.status(500).send("Script execution failed")
}
})

// Handle process errors
spawnedProcess.on("error", err => {
clearTimeout(timeout)
console.error(`Failed to start script process in ${folderName}:`, err)
res.status(500).send("Failed to execute script")
})
} catch (error) {
console.error(`Script execution error in ${folderName}:`, error)
res.status(500).send("Internal server error")
}
})
}
}

module.exports = { ScriptRunner }
9 changes: 8 additions & 1 deletion ScrollHub.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const httpBackend = require("git-http-backend")
const { Particle } = require("scrollsdk/products/Particle.js")
const { ScrollFile, ScrollFileSystem } = require("scroll-cli/scroll.js")
const { CloneCli } = require("scroll-cli/clone.js")
const { ScriptRunner } = require("./ScriptRunner.js")
const packageJson = require("./package.json")
const scrollFs = new ScrollFileSystem()

Expand Down Expand Up @@ -168,6 +169,7 @@ class ScrollHub {
this.initZipRoutes()
this.initCommandRoutes()
this.initSSERoute()
this.initScriptRunner()

this.enableStaticFileServing()

Expand All @@ -176,6 +178,11 @@ class ScrollHub {
return this
}

initScriptRunner() {
this.scriptRunner = new ScriptRunner(this)
this.scriptRunner.init()
}

initSSERoute() {
const { app, globalLogFile } = this
app.get("/requests.htm", (req, res) => {
Expand Down Expand Up @@ -1446,7 +1453,7 @@ scrollVersionLink`
res.redirect(`/index.html?${new URLSearchParams(params).toString()}`)
}

reservedExtensions = "scroll parsers txt html htm css json csv tsv psv ssv pdf js jpg jpeg png gif webp svg heic ico mp3 mp4 mov mkv ogg webm ogv woff2 woff ttf otf tiff tif bmp eps git".split(" ")
reservedExtensions = "scroll parsers txt html htm rb php perl py css json csv tsv psv ssv pdf js jpg jpeg png gif webp svg heic ico mp3 mp4 mov mkv ogg webm ogv woff2 woff ttf otf tiff tif bmp eps git".split(" ")

isValidFolderName(name) {
if (name.length < 2) return false
Expand Down
Loading

0 comments on commit d5084ff

Please sign in to comment.