Skip to content

ADFA-1284 Programmers playground in the LocalWebServer#1048

Merged
hal-eisen-adfa merged 4 commits intostagefrom
ADFA-1284-programmers-playground
Mar 6, 2026
Merged

ADFA-1284 Programmers playground in the LocalWebServer#1048
hal-eisen-adfa merged 4 commits intostagefrom
ADFA-1284-programmers-playground

Conversation

@hal-eisen-adfa
Copy link
Collaborator

Adding the playground/execute endpoint which handles POSTs for the Java compiler
Routes error/output back to the user
Has some HTTP 400, 405, 413 error codes

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

📝 Walkthrough
  • Features Added

    • New POST endpoint /playground/execute to submit Java source for compilation and execution.
    • Java compilation and execution integrated via system javac/java binaries.
    • New data class JavaExecutionResult (compileOutput, runOutput, timedOut, compileTimeMs, timeoutLimit).
    • Accepts URL-encoded form data with a "data" field containing Java source.
    • Temporary working directory created per request and removed after execution.
    • Enforces a 10 KB request payload limit and returns HTTP 400/405/413 as appropriate.
    • Read/write flow updated to use ServerConfig.fileDirPath (MainActivity now passes applicationContext.filesDir.absolutePath).
  • Configuration Changes

    • Breaking change: ServerConfig now requires fileDirPath (application files directory).
  • Error Handling / HTTP Responses

    • 400 Bad Request: missing/invalid Content-Length, interrupted input, or missing/empty data field.
    • 405 Method Not Allowed: non-POST to playground endpoint.
    • 413 Payload Too Large: payload > 10 KB.
    • 500 responses still used for server-side failures; sendError() guards against double-writing when outputStarted is true in callers that set the flag.
  • Timeout and process handling

    • Compilation (javac) has a 60s timeout; execution (java) has a 120s timeout. Processes are forcefully destroyed on timeout and readers are joined with limited waits.
    • Commit message confirms timeout handling was added.
  • Risks and Best-Practice Violations

    ⚠️ Critical / High

    • Arbitrary remote code execution: user-submitted Java is compiled and executed without sandboxing — full system compromise is possible.
    • No OS-level resource limits: CPU, memory, open files, or seccomp/cgroup restrictions are not applied to child processes.
    • Untrusted process output buffered into memory: readers call readText() with no upper bound, risking OOM on large outputs.
    • Hardcoded tool paths: assumes $fileDirPath/usr/bin/javac and .../java exist; no verification or fallback.
    • No rate limiting or authentication: endpoint can be abused to spawn many heavy tasks.

    ⚠️ Medium

    • Simplistic form parsing: custom URL-encoded parser may mishandle edge cases (special chars, malformed encoding, multipart).
    • Potential race conditions: class file deletion and file operations use simple delete() before compile; transient timing issues possible.
    • Partial-response handling: some handlers log a warning about possible double-responses; not all code paths reliably set outputStarted before writes.
    • No input sanitization or policy checks: no filtering of imports, reflection, or System.exit calls.

    ⚠️ Low

    • Temporary directories created under app files dir — consider using a dedicated sandboxed runtime area.
    • Debug/experiment flags rely on files under external storage paths (ad-hoc toggle files).
  • Implementation Notes / Observations

    • compileAndRunJava returns combined compile/run output in plain text; source is echoed back in the 200 response.
    • Work directory named playground_{nanoTime}_{UUID} and deleted recursively in finally block.
    • Process output readers are run in threads and joined with bounded waits; timeouts return informative messages.

Walkthrough

Adds a playground execution endpoint to the local WebServer: ServerConfig gains fileDirPath; new JavaExecutionResult type; POST /playground/execute handling added (form parsing, file creation, javac/java invocation) and request/stream parsing adjusted.

Changes

Cohort / File(s) Summary
App Initialization
app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt
WebServer construction updated to pass ServerConfig with both databasePath and the new fileDirPath (app files directory).
Local Web Server — Playground & I/O
app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt
Added fileDirPath to ServerConfig; introduced JavaExecutionResult; implemented POST /playground/execute flow (Content-Length checks, URL-encoded form parsing, payload size enforcement); added helpers createFileFromPost, parseFormDataField, compileAndRunJava, readLineFromStream; updated header parsing/routing and stream handling.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Server as WebServer
    participant FS as File System
    participant Javac as Compiler (javac)
    participant JavaRun as Runtime (java)

    Client->>Server: POST /playground/execute (form data with source)
    Server->>Server: Read request lines & headers
    Server->>Server: Enforce Content-Length and size limits
    Server->>Server: Extract source field from body
    Server->>FS: Write Playground.java to `fileDirPath`
    FS-->>Server: File created
    Server->>Javac: Compile Playground.java
    Javac-->>Server: Compile output
    alt compile success
        Server->>JavaRun: Execute class
        JavaRun-->>Server: Run output / timeout flag
    end
    Server->>Client: Return JavaExecutionResult (compileOutput, runOutput, timedOut, timings)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 I hopped to the server, tiny paws in the sand,
Posted Java dreams and watched them expand,
Files sprouted neatly where the code chose to stay,
Javac hummed softly, java ran away,
A rabbit's applause — compile, run, hooray! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ADFA-1284 Programmers playground in the LocalWebServer' directly describes the main change—adding a playground feature to the LocalWebServer, which aligns with the changeset that implements the playground/execute endpoint.
Description check ✅ Passed The description accurately describes the changes: adding the playground/execute endpoint for Java compilation, routing outputs to users, and implementing HTTP error codes, all of which are reflected in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ADFA-1284-programmers-playground

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt`:
- Around line 274-277: The playground POST endpoint handling
"playground/execute" currently allows arbitrary Java execution; update
WebServer.kt to enforce an auth boundary by (1) gating access with the existing
experimentsEnabled/debug flag (check experimentsEnabled before calling
handlePlaygroundExecute) and (2) require and validate a per-session unguessable
token issued by the trusted UI (e.g., check a header or cookie token in the same
request and reject if missing/invalid) before invoking handlePlaygroundExecute;
apply the same checks to the other playground routes referenced around
handlePlaygroundExecute (lines ~726-775) to ensure all playground execution
endpoints require both experimentsEnabled and a valid session token.
- Around line 793-799: The current request flow writes to a shared
Playground.java (createFileFromPost) and then uses classFile.exists() to infer
success, which allows stale .class files to execute when a new compile fails;
fix by (1) creating an isolated temp working directory per request (instead of
reusing config.fileDirPath) and writing Playground.java there, (2) before
invoking javac remove any existing Playground.class in that working dir to avoid
running stale bytecode, and (3) check javac’s actual exit code (or
Process.exitValue / Process.waitFor result) rather than relying on
classFile.exists() to determine compile success; update the compile/invocation
logic that currently calls javac and performs the classFile.exists() check to
follow this flow.
- Around line 227-236: The code mixes BufferedReader (reader) for parsing
headers with raw InputStream (input) reads later (e.g., in
handlePlaygroundExecute), which causes buffered body bytes to be lost; fix this
by using a single consistent stream reader: either keep BufferedReader for both
headers and body (after reading headers with reader.readLine(), read the POST
body from reader using read(char[]/readNBytes/skip based on Content-Length) and
convert to bytes) or stop using BufferedReader entirely and implement header
parsing and body reads directly on input (implement readLine on the raw
InputStream and then read exact Content-Length bytes). Update
handlePlaygroundExecute to accept/consume the same reader (or the unified input
wrapper) instead of calling input.read(...) directly, and remove mixed direct
InputStream reads so the request body is read from the same buffered source that
parsed the headers.
- Around line 777-788: The parseFormDataField function currently calls
URLDecoder.decode(...) on both the key and value which can throw
IllegalArgumentException for malformed percent-escapes and causes a 500; wrap
the decode calls in a try-catch that catches IllegalArgumentException and
returns null so the existing 400 Bad Request path is used instead. In practice,
surround the URLDecoder.decode usage in parseFormDataField with a try { ... }
catch (e: IllegalArgumentException) { return null } and ensure both decodes (key
and decoded value) are covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b9d1730-7222-4aa8-a488-558fbfa9585e

📥 Commits

Reviewing files that changed from the base of the PR and between 89ee046 and 3aa87f4.

📒 Files selected for processing (2)
  • app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt
  • app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt

@hal-eisen-adfa hal-eisen-adfa changed the title Programmers playground in the LocalWebServer ADFA-1284 Programmers playground in the LocalWebServer Mar 6, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt`:
- Around line 781-782: The start() accept loop currently calls
createFileFromPost(...) and then compileAndRunJava(...) inline, which blocks the
accept thread; move playground execution onto a bounded Executor to avoid
stalling other requests. Create a class-level ExecutorService (e.g.,
fixed-thread or bounded queue) that is initialized when the server starts and
shutdown in the server stop/close logic, then replace the direct call to
compileAndRunJava(sourceFile) in start() with submitting a Runnable/Callable
that runs compileAndRunJava(sourceFile) (and handles result logging/response
handling asynchronously), ensure you do not block on Future.get() on the accept
thread, and add proper error handling and logging around the submitted task;
reference the start(), compileAndRunJava(), and createFileFromPost() symbols
when making the change.
- Around line 821-852: In compileAndRunJava, the child processes for javac and
java are waited on indefinitely and their stdin remains open; update the code
where ProcessBuilder starts the javac and java processes (variables named javac
and java) to immediately close each process.outputStream, then replace the
unbounded javac.waitFor() and java.waitFor() calls with timed waits (use
waitFor(timeout, TimeUnit.SECONDS) or similar); if a wait returns false, call
destroyForcibly() and optionally wait again to ensure termination, and handle
InterruptedException appropriately before returning the compilation/run output
or a timeout error message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f45ddba7-4ecd-4d12-aece-8e1d4b4983ab

📥 Commits

Reviewing files that changed from the base of the PR and between 3aa87f4 and 5c8188c.

📒 Files selected for processing (1)
  • app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt (2)

788-794: Consider adding Connection: close header for consistency.

Other response handlers in this file include the Connection: close header (e.g., lines 426, 682, 715, 733). While the connection is closed anyway in the finally block of start(), adding it here maintains consistency across all endpoints.

♻️ Suggested fix
 writer.println("HTTP/1.1 200 OK")
 writer.println("Content-Type: text/plain; charset=utf-8")
 writer.println("Content-Length: ${responseBytes.size}")
+writer.println("Connection: close")
 writer.println()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt` around
lines 788 - 794, Add the "Connection: close" response header in the same
response block that writes the status, Content-Type and Content-Length (the code
that calls writer.println(...) and then writes responseBytes) so it matches
other handlers; specifically, insert a writer.println("Connection: close")
before the blank writer.println() in the same request handler, ensuring
start()'s finally still closes sockets as before.

36-42: Remove unused JavaExecutionResult data class or add a TODO comment if it's placeholder code for future enhancement.

The data class is defined at lines 36–42 but never used anywhere in the codebase. The compileAndRunJava function returns a plain String instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt` around
lines 36 - 42, The data class JavaExecutionResult is currently unused while
compileAndRunJava returns a String; either remove JavaExecutionResult to
eliminate dead code or retain it as a placeholder by adding a clear TODO comment
above its declaration explaining intended future use (e.g., to replace the
String return with a structured result), and if you plan to switch
compileAndRunJava later, update that function signature to return
JavaExecutionResult and migrate its callers accordingly; reference
JavaExecutionResult and compileAndRunJava when making the change so reviewers
can find and verify the update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt`:
- Around line 788-794: Add the "Connection: close" response header in the same
response block that writes the status, Content-Type and Content-Length (the code
that calls writer.println(...) and then writes responseBytes) so it matches
other handlers; specifically, insert a writer.println("Connection: close")
before the blank writer.println() in the same request handler, ensuring
start()'s finally still closes sockets as before.
- Around line 36-42: The data class JavaExecutionResult is currently unused
while compileAndRunJava returns a String; either remove JavaExecutionResult to
eliminate dead code or retain it as a placeholder by adding a clear TODO comment
above its declaration explaining intended future use (e.g., to replace the
String return with a structured result), and if you plan to switch
compileAndRunJava later, update that function signature to return
JavaExecutionResult and migrate its callers accordingly; reference
JavaExecutionResult and compileAndRunJava when making the change so reviewers
can find and verify the update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30a24b4c-5a50-4f0e-bb6f-7b19dc39294d

📥 Commits

Reviewing files that changed from the base of the PR and between 5c8188c and 165c0ed.

📒 Files selected for processing (1)
  • app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt

@hal-eisen-adfa hal-eisen-adfa merged commit a3390c6 into stage Mar 6, 2026
2 checks passed
@hal-eisen-adfa hal-eisen-adfa deleted the ADFA-1284-programmers-playground branch March 6, 2026 23:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant