FetchFileContent Documentation
Overview
The FetchFileContent class is an HTTP handler that enables authenticated users to retrieve
the complete contents of files from their website's file structure. It provides a secure API
endpoint for reading file contents, typically used by the web-based file editor or viewer
interface.
Purpose
This handler is part of the multi-tenant hosting service's file management system, allowing
users to access and read their files through the web interface. It is commonly used for editing
operations where the client needs to load existing file contents into an editor, or for
previewing file contents before modification.
Core Functionality
This handler performs the following key operations:
1. Request Validation
- Only accepts POST requests
- Silently ignores non-POST methods (no response sent)
- Expects JSON payload containing the file path and filename
2. Authentication & Authorization
- Extracts and validates the session ID from the request via getJavaSessionId()
- Verifies the session through SessionManager.getUsername()
- Rejects requests with invalid or expired sessions (HTTP 403)
- Ensures users can only access files within their own directory space
3. Path Construction
The handler builds the target file path based on the authenticated user:
- Standard users: {USER_DIR}/{username}/static/{path}{filename}
- Administrator (ADMIN_HOST match): {ADMIN_DIR}/static/{path}{filename}
Where:
- USER_DIR is the base directory for user accounts (likely /home/lukas/users/)
- ADMIN_DIR is the special directory for administrative content (likely /home/lukas/JavaServerProject/www)
- {path} is the optional subdirectory path provided in the JSON request body
- {filename} is the name of the file to read, provided in the JSON request body
4. File Existence Verification
- Checks if the target file exists using Files.exists()
- Returns HTTP 403 if the file does not exist
- Logs missing file attempts with the full path for debugging
5. File Content Retrieval
- Reads the entire file into memory using Files.readAllBytes()
- Returns the raw file contents as-is (binary safe)
- Suitable for text files, but can also handle binary files
6. Response Format
Returns the complete file contents with:
- Content-Type: text/plain; charset=UTF-8 (always, regardless of actual file type)
- Body: The raw bytes of the file
Request Format
Method: POST
Content-Type: application/json
Body:
{
"path": "relative/directory/path/",
"filename": "file.ext"
}
The path value should be relative to the user's static directory and may be
empty for files in the root. The filename is the name of the file to read
(e.g., "index.html", "script.js", "style.css").
Response Format
Content-Type: text/plain; charset=UTF-8
Body: Raw file contents (all bytes)
Response Codes
- 200 OK: File was successfully read and returned
- 403 Forbidden: Invalid session or file does not exist
- No response: Non-POST request (handler returns without sending response)
Security Features
- Session-based authentication: All requests require valid session credentials
- User isolation: Users can only read files within their own directory structure
- Path scoping: File paths are always constructed relative to the user's base directory, preventing directory traversal attacks
- Administrator separation: Special handling for admin accounts with different base paths
- Existence verification: Validates file existence before attempting to read
Error Handling
- Invalid session: Returns 403 Forbidden immediately
- Non-existent file: Logs the missing path and returns 403 Forbidden
- Non-POST request: Handler returns silently without sending any response
- IOException during read: Propagates to calling code (may result in 500 error)
Logging
The handler provides console logging for debugging:
- Logs "No file found at: {path}" when attempting to read a non-existent file
Use Cases
This handler is typically invoked when users:
- Open a file in the web-based code editor
- Preview file contents before making changes
- Load configuration files for editing
- View the contents of HTML, CSS, JavaScript, or PHP files
- Retrieve file data for client-side processing or display
Dependencies
This handler relies on:
- parseJsonToMap(): Converts JSON request body to a key-value map
- getJavaSessionId(): Extracts session identifier from the HTTP request
- SessionManager.getUsername(): Validates session and retrieves the authenticated username
- USER_DIR: Constant defining the base directory for user accounts
- ADMIN_DIR: Constant defining the base directory for administrative content
- ADMIN_HOST: Constant identifying the administrator username
Behavior Notes
- Always returns Content-Type as text/plain, even for binary files or files with other MIME types
- Reads the entire file into memory - may be inefficient for very large files
- Binary-safe: Can read and return any file type, not just text files
- Uses 403 Forbidden for both authentication failures and missing files (could be more specific with 404)
- Non-POST requests are ignored silently without any response, which may cause client timeouts
- Path parameter can be empty or null for files in the root static directory
Performance Considerations
- Memory usage: Files.readAllBytes() loads the entire file into memory. For large files (videos, images, archives), this could cause memory issues
- No streaming: The entire file must be read before any response is sent
- No caching: File is read from disk on every request
Potential Improvements
- Return appropriate MIME types based on file extension instead of always using text/plain
- Use HTTP 404 for missing files instead of 403
- Send HTTP 405 Method Not Allowed for non-POST requests
- Implement streaming for large files to reduce memory usage
- Add file size limits to prevent memory exhaustion
- Implement caching for frequently accessed files
flowchart TD
A(["Start handle"]) --> B{"Request method
== POST?"}
B -->|No| C["Return silently
(No response sent)"]
B -->|Yes| D["Read request body using
InputStreamReader with UTF-8
BufferedReader"]
C --> Z(["End"])
D --> E["Collect body lines into String"]
E --> F["Get sessionId via
getJavaSessionId(exchange)"]
F --> G["Get username from
SessionManager.getUsername(sessionId)"]
G --> H{"Username
is null?"}
H -->|Yes| I["Send 403 Forbidden
Close response body"]
H -->|No| J["Parse JSON body
using parseJsonToMap"]
I --> Z
J --> K["Initialize response byte array"]
K --> L["Build base userPath:
USER_DIR + user + /static/"]
L --> M{"Username ==
ADMIN_HOST?"}
M -->|Yes| N["Override userPath:
ADMIN_DIR + /static/"]
M -->|No| O["Keep user base path"]
N --> P
O --> P
P{"map.get(path) not
empty and not null?"}
P -->|Yes| Q["Append path to userPath"]
P -->|No| R["Keep userPath as is"]
Q --> S["Append filename:
userPath += map.get(filename)"]
R --> S
S --> T["Create Path object:
path = Paths.get(userPath)"]
T --> U{"Files.exists(path)?"}
U -->|No| V["Log: 'No file found at: {path.toString()}'"]
U -->|Yes| W["Read file contents:
response = Files.readAllBytes(path)"]
V --> X["Send 403 Forbidden
Close response body"]
X --> Z
W --> Y["Set Content-Type header:
text/plain; charset=UTF-8"]
Y --> AA["Send 200 response
with response.length"]
AA --> AB["Write response bytes
to OutputStream"]
AB --> AC["Close OutputStream"]
AC --> Z