How Pi‑Web Handles File Path Encoding Between Server and Client
Pi‑Web uses a two‑stage encoding pipeline—segment‑wise URL encoding on the client and array‑based reconstruction on the server—to safely transmit filesystem paths containing spaces, Unicode characters, or Windows backslashes across the Next.js API boundary.
File path encoding is a critical challenge for web‑based file managers. Pi‑Web, an open‑source Next.js application, solves this with a small, auditable toolkit in lib/file-paths.ts and lib/file-links.ts. This article walks through exactly how client‑side encoding, server‑side decoding, and security validation work together to prevent path traversal attacks while supporting cross‑platform paths.
Client‑Side Encoding with encodeFilePathForApi
The client never sends raw filesystem paths in URLs. Instead, every UI component imports encodeFilePathForApi from lib/file-paths.ts (lines 8‑14).
The Encoding Pipeline
- Normalize slashes –
normalizeFilePathSlashesconverts all backslashes to forward slashes. - Split into segments – The path is divided on each "/".
- Encode per segment – Each segment runs through
encodeURIComponent, which handles spaces, Unicode, and reserved characters. - Rejoin – Segments are joined back with "/" for the final URL.
This segment‑wise approach prevents double‑encoding bugs that occur when encoding entire paths at once.
// components/MarkdownBody.tsx – rendering an inline image
const imageSrc = filePath
? `/api/files/${encodeFilePathForApi(filePath)}?type=read`
: src;
The resulting URL for /home/user/project/src/index.ts becomes:
import { encodeFilePathForApi } from '@/lib/file-paths';
const filePath = '/home/user/project/src/index.ts';
const url = `/api/files/${encodeFilePathForApi(filePath)}?type=read`;
// → "/api/files/home%2Fuser%2Fproject%2Fsrc%2Findex.ts?type=read"
Resolving Local File Links with resolveLocalFileHref
Markdown files often contain relative links like ./utils/helpers.ts. Pi‑Web converts these to absolute paths using resolveLocalFileHref in lib/file-links.ts (lines 77‑19).
This helper performs four steps:
- Strip fragments and query strings – Removes
#headingor?line=10suffixes. - Safe percent‑decode – Uses
safeDecodeto reverse any existing encoding without throwing on malformed input. - Normalize slashes – Converts Windows backslashes to forward slashes.
- Resolve against base directory – Joins relative paths to the current working directory (
cwd).
The function also blocks non‑file URLs (e.g., http: schemes) and ensures the resolved path stays within allowed roots.
import { resolveLocalFileHref } from '@/lib/file-links';
const cwd = '/home/user/project';
const href = './utils/helpers.ts';
const absolutePath = resolveLocalFileHref(href, cwd);
// → "/home/user/project/utils/helpers.ts"
Both MarkdownBody and FileExplorer use this to turn markdown links into real paths for the web‑based file viewer.
Server‑Side Decoding in API Routes
Next.js dynamic routes handle URL decoding automatically. The catch‑all route app/api/files/[...path]/route.ts receives params.path as an array of pre‑decoded strings.
The server reconstructs the original path using filePathFromSegments (lines 73‑78):
// In app/api/files/[...path]/route.ts
function filePathFromSegments(segments: string[]): string {
const joined = segments.join('/');
const slashJoined = normalizeSlashes(joined);
if (isWindowsAbsolutePath(slashJoined)) return slashJoined;
return '/' + joined.replace(/^\/+/, '');
}
Key behavior:
- Windows paths – Detected by drive letter pattern (e.g.,
C:/foo), returned unchanged. - POSIX paths – Receives a leading "/" if absent, ensuring absolute path semantics.
Before any filesystem operation, the reconstructed path passes through isFilePathAllowed and related guards in lib/file-access.ts.
Security and Normalization Layers
Pi‑Web's path encoding design prioritizes security through three mechanisms:
| Mechanism | Implementation | Purpose |
|---|---|---|
| Segment encoding | encodeURIComponent per path part |
Prevents interpretation of slashes or special characters as URL structure |
| Root validation | isFilePathAllowed in lib/file-access.ts |
Blocks directory traversal outside whitelisted directories |
| Path normalization | normalizeSlashes everywhere |
Eliminates Windows/macOS/Linux inconsistencies |
The whitelist approach means even a maliciously crafted URL like /api/files/..%2F..%2Fetc%2Fpasswd fails at the validation stage because the decoded path escapes the allowed root.
Where Encoding Happens in the Codebase
| File | Lines | Role |
|---|---|---|
lib/file-paths.ts |
8‑14 | encodeFilePathForApi and slash normalization utilities |
lib/file-links.ts |
77‑19 | resolveLocalFileHref for markdown link resolution |
app/api/files/[...path]/route.ts |
73‑78 | filePathFromSegments for server‑side reconstruction |
lib/file-access.ts |
— | isFilePathAllowed security validation |
components/MarkdownBody.tsx |
70‑77 | Image source encoding example |
components/FileExplorer.tsx |
6‑12 | Directory listing URL construction |
components/FileViewer.tsx |
20‑22 | File content fetch with encoded path |
Summary
- Client encoding –
encodeFilePathForApinormalizes slashes, splits paths, and URL‑encodes each segment individually. - Link resolution –
resolveLocalFileHrefsafely converts relative markdown links to absolute filesystem paths. - Server decoding – Next.js supplies decoded array segments;
filePathFromSegmentsrebuilds the canonical path with platform‑aware handling. - Security – All paths validate against
isFilePathAllowedbefore filesystem access, preventing traversal attacks.
Frequently Asked Questions
How does Pi‑Web handle Windows paths with backslashes?
The normalizeFilePathSlashes function converts all backslashes to forward slashes before encoding. On the server, isWindowsAbsolutePath detects drive‑letter paths and preserves them without adding a leading slash.
Why encode path segments individually instead of the whole path?
Segment‑wise encoding with encodeURIComponent prevents ambiguity. Encoding the full path would turn literal slashes into %2F, breaking the URL structure. By encoding after splitting, Pi‑Web keeps URL path separators intact while protecting segment content.
What prevents directory traversal attacks?
Two layers: (1) segment encoding makes it impossible to inject ../ sequences that survive URL parsing, and (2) isFilePathAllowed in lib/file-access.ts validates the final decoded path against a whitelist of allowed root directories before any file operation.
Does Pi‑Web support Unicode filenames?
Yes. encodeURIComponent handles all Unicode code points, and the segment‑wise approach ensures multi‑byte characters don't interfere with URL parsing. The server receives properly decoded strings in params.path.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →