Skip to content

tzone85/PascalOrDelphiIDE

Repository files navigation

πŸŽ“ Pholani β€” A Friendly Pascal IDE for the Web

Pholani (pronounced Po-LAH-nee, isiZulu for "look") is a small, safe, browser-based Pascal/Delphi IDE built for Grade-12 students. Write Pascal in your browser, click run, see your program come to life β€” no Delphi install, no terminal gymnastics.

Browser demo β€” Hello, World


Table of contents


Why Pholani

Most Pascal tooling for high school is either Windows-only Delphi or a raw fpc terminal. Both lose students. Pholani is the middle ground: a friendly browser editor over the real Free Pascal Compiler, with the rough edges (input handling, sandboxing, error UX) already filed down.

What you get out of the box:

  • ✏️ CodeMirror editor with Pascal syntax highlighting, brackets, F5-to-run.
  • ▢️ Real compile + run against fpc, including stdin support for readln β€” type the input into a side panel and the program reads it.
  • πŸ›‘οΈ Hardened backend β€” Zod-validated requests, strict filename allowlist, per-IP rate limit, Helmet + CSP, per-request sandbox dirs.
  • ⚑ LRU compile cache β€” re-running the same code is instant.
  • πŸ§ͺ Real tests β€” Vitest + Supertest + Playwright (real Chromium against a real server). No tutorial markdown pretending to be tests.
  • πŸ“¦ Docker image for teachers who don't want to install anything.

Quick start

git clone <repo> && cd pholani
npm install
npm start           # β†’ http://localhost:3000

Need the real compiler:

OS Command
macOS brew install fpc
Debian / Ubuntu sudo apt install fpc
Anywhere with Docker docker build -t pholani . && docker run -p 3000:3000 pholani

Don't have fpc and just want to poke around? npm test and npm run test:e2e both work β€” they use a bundled fake compiler.

Install demo

Terminal install demo


readln works in the browser

The original Pholani couldn't run any program that called readln β€” the compiled binary had no stdin. Pholani 2 fixes that with a dedicated Program Input textarea: anything you type there is piped to the running program before it starts.

Browser demo β€” readln program


Architecture

System overview

Module dependency graph:

Module graph

Compile request β€” sequence:

Compile sequence

Source for the diagrams lives at docs/diagrams/*.d2. Re-render with d2 file.d2 file.svg.

Concern Lives in Notes
HTTP bootstrap src/server.js Reads validated config, starts Express.
App composition src/app.js Wires middleware + routes; no I/O.
Security headers / CORS src/middleware/security.js Helmet + CSP, explicit origin allowlist.
Rate limiting src/middleware/rateLimit.js In-memory LRU token bucket; per-IP.
Error mapping src/middleware/errorHandler.js Maps err.statusCode / err.code to JSON.
Routes src/routes/{compile,files,health}.js Thin β€” only Zod parsing + service calls.
Compile pipeline src/services/compiler.js execFile for fpc, spawn for the produced binary with stdin piping.
File storage src/services/fileStore.js Path-traversal-proof load/save into temp/uploads/.
In-memory caches src/services/cache.js lru-cache instances.
Safe filenames / paths / hashing src/lib/ Pure helpers, fully unit-tested.
Env validation src/config/index.js zod schema β€” fail fast on misconfig.

Key invariants:

  1. Per-request sandbox β€” every compile gets a fresh temp/compile/<uuid>/ directory, removed in finally. Concurrent students never collide.
  2. Cache key = SHA-256 of filename::code::stdin β€” identical reruns return instantly without spawning fpc.
  3. spawn + manual stdin pipe for the produced binary, with timeout and output-size caps.

Caching

Cache Key Value Size TTL Purpose
compileCache sha256(filename::code::stdin) {output, error, compilationOutput} 200 30 min Skip recompilation for identical submissions (the common case in a classroom).
fpcCheckCache installed {installed, version, message} 1 5 min Avoid spawning fpc -h on every page load.
rateBuckets req:<ip> / compile:<ip> count 10 000 1 min Per-IP token bucket for general + compile traffic.

All caches use lru-cache. They live in the Node process β€” no Redis or Memcached, no extra ops for a classroom deployment. Reset them in tests with resetAllCaches() from src/services/cache.js.


Security model

Pholani runs student-supplied code on the host. That is inherently risky. Here is what it defends against and what it does not.

Defends against:

Threat Defense Where
Shell / command injection via filename execFile (file form, never a shell) src/services/compiler.js
Path traversal (e.g. ../../etc/passwd) Whitelist ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}\.pas$ + path.resolve containment src/lib/safeFilename.js, src/lib/safePath.js
Body-size DoS express.json({ limit: '100kb' }) + Zod size limits src/app.js, src/routes/compile.js
Compile / run DoS Per-IP token bucket (default 30 compiles / min, 120 requests / min) src/middleware/rateLimit.js
Output-size DoS Run stdout/stderr capped at 64 kB src/services/compiler.js
Infinite loops 10 s SIGKILL timeout src/services/compiler.js
Cross-origin abuse Explicit CORS allowlist src/middleware/security.js
Common header attacks helmet with CSP src/middleware/security.js
Disclosure of host paths / stack traces Sanitized JSON error responses src/middleware/errorHandler.js

Does not defend against:

  • A compiled program calling fpSystem / Exec / making outbound network calls as the server user. Pascal programs run with full Unix privileges of the Node process.
  • A malicious user with shell access to the host.
  • Filesystem exhaustion if you raise PHOLANI_MAX_CODE_BYTES enormously and disable rate limiting.

Therefore: do not expose Pholani to the open internet without an OS-level sandbox. Recommended:

  1. Single-host classroom β€” run inside the bundled Docker image (docker run -p 3000:3000 pholani). The container's filesystem is ephemeral.
  2. Hardened β€” rootless container with --read-only, --tmpfs /app/temp, --cpus/--memory caps, auth reverse proxy.
  3. Optional β€” replace PHOLANI_FPC_BIN with a wrapper that invokes the compiler and produced binary inside nsjail / firejail / bubblewrap.

Configuration

All knobs are environment variables validated by src/config/index.js:

Variable Default Purpose
PORT 3000 HTTP listen port
NODE_ENV development development | test | production
PHOLANI_TEMP_DIR ./temp Scratch dir for uploads + compile sandboxes
PHOLANI_FPC_BIN fpc Path to the compiler (override for sandbox wrappers or tests)
PHOLANI_CORS_ORIGINS http://localhost:3000 Comma-separated allowlist
PHOLANI_MAX_CODE_BYTES 100000 Hard ceiling on submitted source size
PHOLANI_COMPILE_TIMEOUT_MS 30000 Kill the compiler if it stalls
PHOLANI_RUN_TIMEOUT_MS 10000 Kill the produced binary if it loops
PHOLANI_RUN_OUTPUT_BYTES 65536 Clamp run stdout/stderr
PHOLANI_RATE_COMPILE_PER_MIN 30 Per-IP compile budget
PHOLANI_RATE_REQUEST_PER_MIN 120 Per-IP general budget

Invalid values cause the server to crash on boot with a Zod error β€” by design.


Tests

Command What it does
npm test Vitest unit + API tests with v8 coverage. Fails below 80% lines / functions / statements.
npm run test:unit Just tests/unit/.
npm run test:api Just tests/api/ (Supertest against an in-process Express app).
npm run test:e2e Playwright: spins a real server on port 3456, drives Chromium, runs hello-world + readln + error scenarios.
PHOLANI_RECORD_DEMOS=1 npm run test:e2e -- tests/e2e/demo.spec.js Re-records the browser demo gifs in docs/demos/.

The bundled tests/fixtures/fake-fpc.sh lets the entire pipeline run on machines without Free Pascal installed.


Project layout

.
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ server.js              # bootstrap
β”‚   β”œβ”€β”€ app.js                 # composition root
β”‚   β”œβ”€β”€ config/index.js        # Zod-validated env
β”‚   β”œβ”€β”€ middleware/{security,rateLimit,errorHandler}.js
β”‚   β”œβ”€β”€ routes/{compile,files,health}.js
β”‚   β”œβ”€β”€ services/{compiler,fileStore,cache}.js
β”‚   └── lib/{safeFilename,safePath,sha}.js
β”œβ”€β”€ public/                    # browser SPA (index.html, script.js, styles.css)
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ unit/                  # Vitest
β”‚   β”œβ”€β”€ api/                   # Supertest
β”‚   β”œβ”€β”€ e2e/                   # Playwright
β”‚   └── fixtures/fake-fpc.sh
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ diagrams/              # *.d2 source + *.svg rendered
β”‚   β”œβ”€β”€ demos/                 # *.tape + *.gif
β”‚   β”œβ”€β”€ learning-examples/     # Pascal walkthroughs for students
β”‚   └── pholani-overhaul-design.md  # full v2 design spec
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ .github/workflows/ci.yml
β”œβ”€β”€ CLAUDE.md
β”œβ”€β”€ CONTRIBUTING.md
└── README.md

Further docs


License

MIT.

About

We don't have windows machines at home and my daughter needs to practice coding in Delphi for school. That's what's given rise to this small project.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors