# How Pi‑Web Handles File Path Encoding Between Server and Client

> Discover how Pi-Web safely handles file path encoding between server and client using a two-stage pipeline to transmit filesystem paths with spaces, Unicode, or backslashes.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: how-to-guide
- Published: 2026-08-13

---

**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`](https://github.com/agegr/pi-web/blob/main/lib/file-paths.ts) and [`lib/file-links.ts`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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.

```tsx
// 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`](https://github.com/agegr/pi-web/blob/main//home/user/project/src/index.ts) becomes:

```tsx
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`](https://github.com/agegr/pi-web/blob/main/./utils/helpers.ts). Pi‑Web converts these to absolute paths using **`resolveLocalFileHref`** in [`lib/file-links.ts`](https://github.com/agegr/pi-web/blob/main/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.

```tsx
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):

```ts
// 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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/lib/file-paths.ts) | 8‑14 | `encodeFilePathForApi` and slash normalization utilities |
| [`lib/file-links.ts`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts) | — | `isFilePathAllowed` security validation |
| [`components/MarkdownBody.tsx`](https://github.com/agegr/pi-web/blob/main/components/MarkdownBody.tsx) | 70‑77 | Image source encoding example |
| [`components/FileExplorer.tsx`](https://github.com/agegr/pi-web/blob/main/components/FileExplorer.tsx) | 6‑12 | Directory listing URL construction |
| [`components/FileViewer.tsx`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`.