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

  1. Normalize slashes – normalizeFilePathSlashes converts all backslashes to forward slashes.
  2. Split into segments – The path is divided on each "/".
  3. Encode per segment – Each segment runs through encodeURIComponent, which handles spaces, Unicode, and reserved characters.
  4. 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"

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 #heading or ?line=10 suffixes.
  • Safe percent‑decode – Uses safeDecode to 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 – encodeFilePathForApi normalizes slashes, splits paths, and URL‑encodes each segment individually.
  • Link resolution – resolveLocalFileHref safely converts relative markdown links to absolute filesystem paths.
  • Server decoding – Next.js supplies decoded array segments; filePathFromSegments rebuilds the canonical path with platform‑aware handling.
  • Security – All paths validate against isFilePathAllowed before 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →