Skip to content

Commit 52f02f0

Browse files
committed
readfile
1 parent db09b4d commit 52f02f0

File tree

7 files changed

+762
-1
lines changed

7 files changed

+762
-1
lines changed

.changeset/whole-games-tap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudflare/sandbox": patch
3+
---
4+
5+
readFile

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export default {
5656
- `gitCheckout(repoUrl: string, options: { branch?: string; targetDir?: string; stream?: boolean })`: Checkout a git repository in the sandbox.
5757
- `mkdir(path: string, options: { recursive?: boolean; stream?: boolean })`: Create a directory in the sandbox.
5858
- `writeFile(path: string, content: string, options: { encoding?: string; stream?: boolean })`: Write content to a file in the sandbox.
59+
- `readFile(path: string, options: { encoding?: string; stream?: boolean })`: Read content from a file in the sandbox.
5960
- `deleteFile(path: string, options?: { stream?: boolean })`: Delete a file from the sandbox.
6061
- `renameFile(oldPath: string, newPath: string, options?: { stream?: boolean })`: Rename a file in the sandbox.
6162
- `moveFile(sourcePath: string, destinationPath: string, options?: { stream?: boolean })`: Move a file from one location to another in the sandbox.

packages/sandbox/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export default {
5656
- `gitCheckout(repoUrl: string, options: { branch?: string; targetDir?: string; stream?: boolean })`: Checkout a git repository in the sandbox.
5757
- `mkdir(path: string, options: { recursive?: boolean; stream?: boolean })`: Create a directory in the sandbox.
5858
- `writeFile(path: string, content: string, options: { encoding?: string; stream?: boolean })`: Write content to a file in the sandbox.
59+
- `readFile(path: string, options: { encoding?: string; stream?: boolean })`: Read content from a file in the sandbox.
5960
- `deleteFile(path: string, options?: { stream?: boolean })`: Delete a file from the sandbox.
6061
- `renameFile(oldPath: string, newPath: string, options?: { stream?: boolean })`: Rename a file in the sandbox.
6162
- `moveFile(sourcePath: string, destinationPath: string, options?: { stream?: boolean })`: Move a file from one location to another in the sandbox.

packages/sandbox/container_src/index.ts

Lines changed: 281 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { spawn } from "node:child_process";
2-
import { mkdir, rename, unlink, writeFile } from "node:fs/promises";
2+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
33
import { dirname } from "node:path";
44
import { serve } from "bun";
55

@@ -28,6 +28,12 @@ interface WriteFileRequest {
2828
sessionId?: string;
2929
}
3030

31+
interface ReadFileRequest {
32+
path: string;
33+
encoding?: string;
34+
sessionId?: string;
35+
}
36+
3137
interface DeleteFileRequest {
3238
path: string;
3339
sessionId?: string;
@@ -297,6 +303,18 @@ const server = serve({
297303
}
298304
break;
299305

306+
case "/api/read":
307+
if (req.method === "POST") {
308+
return handleReadFileRequest(req, corsHeaders);
309+
}
310+
break;
311+
312+
case "/api/read/stream":
313+
if (req.method === "POST") {
314+
return handleStreamingReadFileRequest(req, corsHeaders);
315+
}
316+
break;
317+
300318
case "/api/delete":
301319
if (req.method === "POST") {
302320
return handleDeleteFileRequest(req, corsHeaders);
@@ -1476,6 +1494,235 @@ async function handleStreamingWriteFileRequest(
14761494
}
14771495
}
14781496

1497+
async function handleReadFileRequest(
1498+
req: Request,
1499+
corsHeaders: Record<string, string>
1500+
): Promise<Response> {
1501+
try {
1502+
const body = (await req.json()) as ReadFileRequest;
1503+
const { path, encoding = "utf-8", sessionId } = body;
1504+
1505+
if (!path || typeof path !== "string") {
1506+
return new Response(
1507+
JSON.stringify({
1508+
error: "Path is required and must be a string",
1509+
}),
1510+
{
1511+
headers: {
1512+
"Content-Type": "application/json",
1513+
...corsHeaders,
1514+
},
1515+
status: 400,
1516+
}
1517+
);
1518+
}
1519+
1520+
// Basic safety check - prevent dangerous paths
1521+
const dangerousPatterns = [
1522+
/^\/$/, // Root directory
1523+
/^\/etc/, // System directories
1524+
/^\/var/, // System directories
1525+
/^\/usr/, // System directories
1526+
/^\/bin/, // System directories
1527+
/^\/sbin/, // System directories
1528+
/^\/boot/, // System directories
1529+
/^\/dev/, // System directories
1530+
/^\/proc/, // System directories
1531+
/^\/sys/, // System directories
1532+
/^\/tmp\/\.\./, // Path traversal attempts
1533+
/\.\./, // Path traversal attempts
1534+
];
1535+
1536+
if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1537+
return new Response(
1538+
JSON.stringify({
1539+
error: "Dangerous path not allowed",
1540+
}),
1541+
{
1542+
headers: {
1543+
"Content-Type": "application/json",
1544+
...corsHeaders,
1545+
},
1546+
status: 400,
1547+
}
1548+
);
1549+
}
1550+
1551+
console.log(`[Server] Reading file: ${path}`);
1552+
1553+
const result = await executeReadFile(path, encoding, sessionId);
1554+
1555+
return new Response(
1556+
JSON.stringify({
1557+
content: result.content,
1558+
exitCode: result.exitCode,
1559+
path,
1560+
success: result.success,
1561+
timestamp: new Date().toISOString(),
1562+
}),
1563+
{
1564+
headers: {
1565+
"Content-Type": "application/json",
1566+
...corsHeaders,
1567+
},
1568+
}
1569+
);
1570+
} catch (error) {
1571+
console.error("[Server] Error in handleReadFileRequest:", error);
1572+
return new Response(
1573+
JSON.stringify({
1574+
error: "Failed to read file",
1575+
message: error instanceof Error ? error.message : "Unknown error",
1576+
}),
1577+
{
1578+
headers: {
1579+
"Content-Type": "application/json",
1580+
...corsHeaders,
1581+
},
1582+
status: 500,
1583+
}
1584+
);
1585+
}
1586+
}
1587+
1588+
async function handleStreamingReadFileRequest(
1589+
req: Request,
1590+
corsHeaders: Record<string, string>
1591+
): Promise<Response> {
1592+
try {
1593+
const body = (await req.json()) as ReadFileRequest;
1594+
const { path, encoding = "utf-8", sessionId } = body;
1595+
1596+
if (!path || typeof path !== "string") {
1597+
return new Response(
1598+
JSON.stringify({
1599+
error: "Path is required and must be a string",
1600+
}),
1601+
{
1602+
headers: {
1603+
"Content-Type": "application/json",
1604+
...corsHeaders,
1605+
},
1606+
status: 400,
1607+
}
1608+
);
1609+
}
1610+
1611+
// Basic safety check - prevent dangerous paths
1612+
const dangerousPatterns = [
1613+
/^\/$/, // Root directory
1614+
/^\/etc/, // System directories
1615+
/^\/var/, // System directories
1616+
/^\/usr/, // System directories
1617+
/^\/bin/, // System directories
1618+
/^\/sbin/, // System directories
1619+
/^\/boot/, // System directories
1620+
/^\/dev/, // System directories
1621+
/^\/proc/, // System directories
1622+
/^\/sys/, // System directories
1623+
/^\/tmp\/\.\./, // Path traversal attempts
1624+
/\.\./, // Path traversal attempts
1625+
];
1626+
1627+
if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1628+
return new Response(
1629+
JSON.stringify({
1630+
error: "Dangerous path not allowed",
1631+
}),
1632+
{
1633+
headers: {
1634+
"Content-Type": "application/json",
1635+
...corsHeaders,
1636+
},
1637+
status: 400,
1638+
}
1639+
);
1640+
}
1641+
1642+
console.log(`[Server] Reading file (streaming): ${path}`);
1643+
1644+
const stream = new ReadableStream({
1645+
start(controller) {
1646+
(async () => {
1647+
try {
1648+
// Send command start event
1649+
controller.enqueue(
1650+
new TextEncoder().encode(
1651+
`data: ${JSON.stringify({
1652+
path,
1653+
timestamp: new Date().toISOString(),
1654+
type: "command_start",
1655+
})}\n\n`
1656+
)
1657+
);
1658+
1659+
// Read the file
1660+
const content = await readFile(path, {
1661+
encoding: encoding as BufferEncoding,
1662+
});
1663+
1664+
console.log(`[Server] File read successfully: ${path}`);
1665+
1666+
// Send command completion event
1667+
controller.enqueue(
1668+
new TextEncoder().encode(
1669+
`data: ${JSON.stringify({
1670+
content,
1671+
path,
1672+
success: true,
1673+
timestamp: new Date().toISOString(),
1674+
type: "command_complete",
1675+
})}\n\n`
1676+
)
1677+
);
1678+
1679+
controller.close();
1680+
} catch (error) {
1681+
console.error(`[Server] Error reading file: ${path}`, error);
1682+
1683+
controller.enqueue(
1684+
new TextEncoder().encode(
1685+
`data: ${JSON.stringify({
1686+
error:
1687+
error instanceof Error ? error.message : "Unknown error",
1688+
path,
1689+
type: "error",
1690+
})}\n\n`
1691+
)
1692+
);
1693+
1694+
controller.close();
1695+
}
1696+
})();
1697+
},
1698+
});
1699+
1700+
return new Response(stream, {
1701+
headers: {
1702+
"Cache-Control": "no-cache",
1703+
Connection: "keep-alive",
1704+
"Content-Type": "text/event-stream",
1705+
...corsHeaders,
1706+
},
1707+
});
1708+
} catch (error) {
1709+
console.error("[Server] Error in handleStreamingReadFileRequest:", error);
1710+
return new Response(
1711+
JSON.stringify({
1712+
error: "Failed to read file",
1713+
message: error instanceof Error ? error.message : "Unknown error",
1714+
}),
1715+
{
1716+
headers: {
1717+
"Content-Type": "application/json",
1718+
...corsHeaders,
1719+
},
1720+
status: 500,
1721+
}
1722+
);
1723+
}
1724+
}
1725+
14791726
async function handleDeleteFileRequest(
14801727
req: Request,
14811728
corsHeaders: Record<string, string>
@@ -2506,6 +2753,37 @@ function executeWriteFile(
25062753
});
25072754
}
25082755

2756+
function executeReadFile(
2757+
path: string,
2758+
encoding: string,
2759+
sessionId?: string
2760+
): Promise<{
2761+
success: boolean;
2762+
exitCode: number;
2763+
content: string;
2764+
}> {
2765+
return new Promise((resolve, reject) => {
2766+
(async () => {
2767+
try {
2768+
// Read the file
2769+
const content = await readFile(path, {
2770+
encoding: encoding as BufferEncoding,
2771+
});
2772+
2773+
console.log(`[Server] File read successfully: ${path}`);
2774+
resolve({
2775+
content,
2776+
exitCode: 0,
2777+
success: true,
2778+
});
2779+
} catch (error) {
2780+
console.error(`[Server] Error reading file: ${path}`, error);
2781+
reject(error);
2782+
}
2783+
})();
2784+
});
2785+
}
2786+
25092787
function executeDeleteFile(
25102788
path: string,
25112789
sessionId?: string
@@ -2610,6 +2888,8 @@ console.log(` POST /api/mkdir - Create a directory`);
26102888
console.log(` POST /api/mkdir/stream - Create a directory (streaming)`);
26112889
console.log(` POST /api/write - Write a file`);
26122890
console.log(` POST /api/write/stream - Write a file (streaming)`);
2891+
console.log(` POST /api/read - Read a file`);
2892+
console.log(` POST /api/read/stream - Read a file (streaming)`);
26132893
console.log(` POST /api/delete - Delete a file`);
26142894
console.log(` POST /api/delete/stream - Delete a file (streaming)`);
26152895
console.log(` POST /api/rename - Rename a file`);

0 commit comments

Comments
 (0)