@cloudflare/sandbox 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/Dockerfile +9 -11
- package/README.md +69 -7
- package/container_src/control-process.ts +784 -0
- package/container_src/handler/exec.ts +99 -254
- package/container_src/handler/file.ts +179 -837
- package/container_src/handler/git.ts +28 -80
- package/container_src/handler/process.ts +443 -515
- package/container_src/handler/session.ts +92 -0
- package/container_src/index.ts +68 -130
- package/container_src/isolation.ts +1038 -0
- package/container_src/shell-escape.ts +42 -0
- package/container_src/types.ts +27 -13
- package/dist/{chunk-HHUDRGPY.js → chunk-BEQUGUY4.js} +2 -2
- package/dist/{chunk-CKIGERRS.js → chunk-LFLJGISB.js} +240 -264
- package/dist/chunk-LFLJGISB.js.map +1 -0
- package/dist/{chunk-3CQ6THKA.js → chunk-SMUEY5JR.js} +85 -103
- package/dist/chunk-SMUEY5JR.js.map +1 -0
- package/dist/{client-Ce40ujDF.d.ts → client-Dny_ro_v.d.ts} +41 -25
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -9
- package/dist/interpreter.d.ts +1 -1
- package/dist/jupyter-client.d.ts +1 -1
- package/dist/jupyter-client.js +2 -2
- package/dist/request-handler.d.ts +1 -1
- package/dist/request-handler.js +3 -5
- package/dist/sandbox.d.ts +1 -1
- package/dist/sandbox.js +3 -5
- package/dist/types.d.ts +10 -21
- package/dist/types.js +35 -9
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/client.ts +120 -135
- package/src/index.ts +8 -0
- package/src/sandbox.ts +290 -331
- package/src/types.ts +15 -24
- package/dist/chunk-3CQ6THKA.js.map +0 -1
- package/dist/chunk-6EWSYSO7.js +0 -46
- package/dist/chunk-6EWSYSO7.js.map +0 -1
- package/dist/chunk-CKIGERRS.js.map +0 -1
- /package/dist/{chunk-HHUDRGPY.js.map → chunk-BEQUGUY4.js.map} +0 -0
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
1
|
+
import type { SessionManager } from "../isolation";
|
|
4
2
|
import type {
|
|
5
3
|
DeleteFileRequest,
|
|
6
4
|
ListFilesRequest,
|
|
@@ -8,154 +6,111 @@ import type {
|
|
|
8
6
|
MoveFileRequest,
|
|
9
7
|
ReadFileRequest,
|
|
10
8
|
RenameFileRequest,
|
|
11
|
-
SessionData,
|
|
12
9
|
WriteFileRequest
|
|
13
10
|
} from "../types";
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
session.activeProcess = mkdirChild;
|
|
12
|
+
// Common path validation patterns
|
|
13
|
+
const DANGEROUS_PATH_PATTERNS = [
|
|
14
|
+
/^\/$/, // Root directory
|
|
15
|
+
/^\/etc/, // System directories
|
|
16
|
+
/^\/var/, // System directories
|
|
17
|
+
/^\/usr/, // System directories
|
|
18
|
+
/^\/bin/, // System directories
|
|
19
|
+
/^\/sbin/, // System directories
|
|
20
|
+
/^\/boot/, // System directories
|
|
21
|
+
/^\/dev/, // System directories
|
|
22
|
+
/^\/proc/, // System directories
|
|
23
|
+
/^\/sys/, // System directories
|
|
24
|
+
/^\/tmp\/\.\./, // Path traversal attempts
|
|
25
|
+
/\.\./, // Path traversal attempts
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// Path validation utility
|
|
29
|
+
function validatePath(...paths: string[]): string | null {
|
|
30
|
+
for (const path of paths) {
|
|
31
|
+
if (!path || typeof path !== "string") {
|
|
32
|
+
return "Path is required and must be a string";
|
|
37
33
|
}
|
|
34
|
+
|
|
35
|
+
if (DANGEROUS_PATH_PATTERNS.some((pattern) => pattern.test(path))) {
|
|
36
|
+
return "Dangerous path not allowed";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (code === 0) {
|
|
58
|
-
console.log(`[Server] Directory created successfully: ${path}`);
|
|
59
|
-
resolve({
|
|
60
|
-
exitCode: code || 0,
|
|
61
|
-
stderr,
|
|
62
|
-
stdout,
|
|
63
|
-
success: true,
|
|
64
|
-
});
|
|
65
|
-
} else {
|
|
66
|
-
console.error(
|
|
67
|
-
`[Server] Failed to create directory: ${path}, Exit code: ${code}`
|
|
68
|
-
);
|
|
69
|
-
resolve({
|
|
70
|
-
exitCode: code || 1,
|
|
71
|
-
stderr,
|
|
72
|
-
stdout,
|
|
73
|
-
success: false,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
mkdirChild.on("error", (error) => {
|
|
79
|
-
// Clear the active process reference
|
|
80
|
-
if (sessionId && sessions.has(sessionId)) {
|
|
81
|
-
const session = sessions.get(sessionId)!;
|
|
82
|
-
session.activeProcess = null;
|
|
83
|
-
}
|
|
42
|
+
// Common error response utility
|
|
43
|
+
function createPathErrorResponse(
|
|
44
|
+
error: string,
|
|
45
|
+
corsHeaders: Record<string, string>
|
|
46
|
+
): Response {
|
|
47
|
+
return new Response(
|
|
48
|
+
JSON.stringify({ error }),
|
|
49
|
+
{
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
...corsHeaders,
|
|
53
|
+
},
|
|
54
|
+
status: 400,
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
}
|
|
84
58
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
59
|
+
// Common server error response utility
|
|
60
|
+
function createServerErrorResponse(
|
|
61
|
+
operation: string,
|
|
62
|
+
error: unknown,
|
|
63
|
+
corsHeaders: Record<string, string>
|
|
64
|
+
): Response {
|
|
65
|
+
console.error(`[Server] Error in ${operation}:`, error);
|
|
66
|
+
return new Response(
|
|
67
|
+
JSON.stringify({
|
|
68
|
+
error: `Failed to ${operation.replace('handle', '').replace('Request', '').toLowerCase().replace('file', ' file')}`,
|
|
69
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
70
|
+
}),
|
|
71
|
+
{
|
|
72
|
+
headers: {
|
|
73
|
+
"Content-Type": "application/json",
|
|
74
|
+
...corsHeaders,
|
|
75
|
+
},
|
|
76
|
+
status: 500,
|
|
77
|
+
}
|
|
78
|
+
);
|
|
89
79
|
}
|
|
90
80
|
|
|
91
81
|
export async function handleMkdirRequest(
|
|
92
|
-
sessions: Map<string, SessionData>,
|
|
93
82
|
req: Request,
|
|
94
|
-
corsHeaders: Record<string, string
|
|
83
|
+
corsHeaders: Record<string, string>,
|
|
84
|
+
sessionManager: SessionManager
|
|
95
85
|
): Promise<Response> {
|
|
96
86
|
try {
|
|
97
87
|
const body = (await req.json()) as MkdirRequest;
|
|
98
88
|
const { path, recursive = false, sessionId } = body;
|
|
99
89
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}),
|
|
105
|
-
{
|
|
106
|
-
headers: {
|
|
107
|
-
"Content-Type": "application/json",
|
|
108
|
-
...corsHeaders,
|
|
109
|
-
},
|
|
110
|
-
status: 400,
|
|
111
|
-
}
|
|
112
|
-
);
|
|
90
|
+
// Validate path
|
|
91
|
+
const pathError = validatePath(path);
|
|
92
|
+
if (pathError) {
|
|
93
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
113
94
|
}
|
|
114
95
|
|
|
115
|
-
|
|
116
|
-
const dangerousPatterns = [
|
|
117
|
-
/^\/$/, // Root directory
|
|
118
|
-
/^\/etc/, // System directories
|
|
119
|
-
/^\/var/, // System directories
|
|
120
|
-
/^\/usr/, // System directories
|
|
121
|
-
/^\/bin/, // System directories
|
|
122
|
-
/^\/sbin/, // System directories
|
|
123
|
-
/^\/boot/, // System directories
|
|
124
|
-
/^\/dev/, // System directories
|
|
125
|
-
/^\/proc/, // System directories
|
|
126
|
-
/^\/sys/, // System directories
|
|
127
|
-
/^\/tmp\/\.\./, // Path traversal attempts
|
|
128
|
-
/\.\./, // Path traversal attempts
|
|
129
|
-
];
|
|
96
|
+
console.log(`[Server] Creating directory: ${path} (recursive: ${recursive})${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
130
97
|
|
|
131
|
-
if
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"Content-Type": "application/json",
|
|
139
|
-
...corsHeaders,
|
|
140
|
-
},
|
|
141
|
-
status: 400,
|
|
142
|
-
}
|
|
143
|
-
);
|
|
98
|
+
// Use specific session if provided, otherwise default session
|
|
99
|
+
const result = sessionId
|
|
100
|
+
? await sessionManager.getSession(sessionId)?.mkdirOperation(path, recursive)
|
|
101
|
+
: await sessionManager.mkdir(path, recursive);
|
|
102
|
+
|
|
103
|
+
if (!result) {
|
|
104
|
+
return createServerErrorResponse("handleMkdirRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
144
105
|
}
|
|
145
106
|
|
|
146
|
-
console.log(
|
|
147
|
-
`[Server] Creating directory: ${path} (recursive: ${recursive})`
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
const result = await executeMkdir(sessions, path, recursive, sessionId);
|
|
151
|
-
|
|
152
107
|
return new Response(
|
|
153
108
|
JSON.stringify({
|
|
154
109
|
exitCode: result.exitCode,
|
|
155
110
|
path,
|
|
156
111
|
recursive,
|
|
157
|
-
stderr:
|
|
158
|
-
stdout:
|
|
112
|
+
stderr: "",
|
|
113
|
+
stdout: "",
|
|
159
114
|
success: result.success,
|
|
160
115
|
timestamp: new Date().toISOString(),
|
|
161
116
|
}),
|
|
@@ -167,120 +122,36 @@ export async function handleMkdirRequest(
|
|
|
167
122
|
}
|
|
168
123
|
);
|
|
169
124
|
} catch (error) {
|
|
170
|
-
|
|
171
|
-
return new Response(
|
|
172
|
-
JSON.stringify({
|
|
173
|
-
error: "Failed to create directory",
|
|
174
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
175
|
-
}),
|
|
176
|
-
{
|
|
177
|
-
headers: {
|
|
178
|
-
"Content-Type": "application/json",
|
|
179
|
-
...corsHeaders,
|
|
180
|
-
},
|
|
181
|
-
status: 500,
|
|
182
|
-
}
|
|
183
|
-
);
|
|
125
|
+
return createServerErrorResponse("handleMkdirRequest", error, corsHeaders);
|
|
184
126
|
}
|
|
185
127
|
}
|
|
186
128
|
|
|
187
|
-
|
|
188
|
-
function executeWriteFile(
|
|
189
|
-
path: string,
|
|
190
|
-
content: string,
|
|
191
|
-
encoding: string,
|
|
192
|
-
sessionId?: string
|
|
193
|
-
): Promise<{
|
|
194
|
-
success: boolean;
|
|
195
|
-
exitCode: number;
|
|
196
|
-
}> {
|
|
197
|
-
return new Promise((resolve, reject) => {
|
|
198
|
-
(async () => {
|
|
199
|
-
try {
|
|
200
|
-
// Ensure the directory exists
|
|
201
|
-
const dir = dirname(path);
|
|
202
|
-
if (dir !== ".") {
|
|
203
|
-
await mkdir(dir, { recursive: true });
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Write the file
|
|
207
|
-
await writeFile(path, content, {
|
|
208
|
-
encoding: encoding as BufferEncoding,
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
console.log(`[Server] File written successfully: ${path}`);
|
|
212
|
-
resolve({
|
|
213
|
-
exitCode: 0,
|
|
214
|
-
success: true,
|
|
215
|
-
});
|
|
216
|
-
} catch (error) {
|
|
217
|
-
console.error(`[Server] Error writing file: ${path}`, error);
|
|
218
|
-
reject(error);
|
|
219
|
-
}
|
|
220
|
-
})();
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
129
|
export async function handleWriteFileRequest(
|
|
225
130
|
req: Request,
|
|
226
|
-
corsHeaders: Record<string, string
|
|
131
|
+
corsHeaders: Record<string, string>,
|
|
132
|
+
sessionManager: SessionManager
|
|
227
133
|
): Promise<Response> {
|
|
228
134
|
try {
|
|
229
135
|
const body = (await req.json()) as WriteFileRequest;
|
|
230
136
|
const { path, content, encoding = "utf-8", sessionId } = body;
|
|
231
137
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}),
|
|
237
|
-
{
|
|
238
|
-
headers: {
|
|
239
|
-
"Content-Type": "application/json",
|
|
240
|
-
...corsHeaders,
|
|
241
|
-
},
|
|
242
|
-
status: 400,
|
|
243
|
-
}
|
|
244
|
-
);
|
|
138
|
+
// Validate path
|
|
139
|
+
const pathError = validatePath(path);
|
|
140
|
+
if (pathError) {
|
|
141
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
245
142
|
}
|
|
246
143
|
|
|
247
|
-
|
|
248
|
-
const dangerousPatterns = [
|
|
249
|
-
/^\/$/, // Root directory
|
|
250
|
-
/^\/etc/, // System directories
|
|
251
|
-
/^\/var/, // System directories
|
|
252
|
-
/^\/usr/, // System directories
|
|
253
|
-
/^\/bin/, // System directories
|
|
254
|
-
/^\/sbin/, // System directories
|
|
255
|
-
/^\/boot/, // System directories
|
|
256
|
-
/^\/dev/, // System directories
|
|
257
|
-
/^\/proc/, // System directories
|
|
258
|
-
/^\/sys/, // System directories
|
|
259
|
-
/^\/tmp\/\.\./, // Path traversal attempts
|
|
260
|
-
/\.\./, // Path traversal attempts
|
|
261
|
-
];
|
|
144
|
+
console.log(`[Server] Writing file: ${path} (content length: ${content.length})${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
262
145
|
|
|
263
|
-
if
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
"Content-Type": "application/json",
|
|
271
|
-
...corsHeaders,
|
|
272
|
-
},
|
|
273
|
-
status: 400,
|
|
274
|
-
}
|
|
275
|
-
);
|
|
146
|
+
// Use specific session if provided, otherwise default session
|
|
147
|
+
const result = sessionId
|
|
148
|
+
? await sessionManager.getSession(sessionId)?.writeFileOperation(path, content, encoding)
|
|
149
|
+
: await sessionManager.writeFile(path, content, encoding);
|
|
150
|
+
|
|
151
|
+
if (!result) {
|
|
152
|
+
return createServerErrorResponse("handleWriteFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
276
153
|
}
|
|
277
154
|
|
|
278
|
-
console.log(
|
|
279
|
-
`[Server] Writing file: ${path} (content length: ${content.length})`
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
const result = await executeWriteFile(path, content, encoding, sessionId);
|
|
283
|
-
|
|
284
155
|
return new Response(
|
|
285
156
|
JSON.stringify({
|
|
286
157
|
exitCode: result.exitCode,
|
|
@@ -296,113 +167,36 @@ export async function handleWriteFileRequest(
|
|
|
296
167
|
}
|
|
297
168
|
);
|
|
298
169
|
} catch (error) {
|
|
299
|
-
|
|
300
|
-
return new Response(
|
|
301
|
-
JSON.stringify({
|
|
302
|
-
error: "Failed to write file",
|
|
303
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
304
|
-
}),
|
|
305
|
-
{
|
|
306
|
-
headers: {
|
|
307
|
-
"Content-Type": "application/json",
|
|
308
|
-
...corsHeaders,
|
|
309
|
-
},
|
|
310
|
-
status: 500,
|
|
311
|
-
}
|
|
312
|
-
);
|
|
170
|
+
return createServerErrorResponse("handleWriteFileRequest", error, corsHeaders);
|
|
313
171
|
}
|
|
314
172
|
}
|
|
315
173
|
|
|
316
|
-
|
|
317
|
-
function executeReadFile(
|
|
318
|
-
path: string,
|
|
319
|
-
encoding: string,
|
|
320
|
-
sessionId?: string
|
|
321
|
-
): Promise<{
|
|
322
|
-
success: boolean;
|
|
323
|
-
exitCode: number;
|
|
324
|
-
content: string;
|
|
325
|
-
}> {
|
|
326
|
-
return new Promise((resolve, reject) => {
|
|
327
|
-
(async () => {
|
|
328
|
-
try {
|
|
329
|
-
// Read the file
|
|
330
|
-
const content = await readFile(path, {
|
|
331
|
-
encoding: encoding as BufferEncoding,
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
console.log(`[Server] File read successfully: ${path}`);
|
|
335
|
-
resolve({
|
|
336
|
-
content,
|
|
337
|
-
exitCode: 0,
|
|
338
|
-
success: true,
|
|
339
|
-
});
|
|
340
|
-
} catch (error) {
|
|
341
|
-
console.error(`[Server] Error reading file: ${path}`, error);
|
|
342
|
-
reject(error);
|
|
343
|
-
}
|
|
344
|
-
})();
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
|
|
348
174
|
export async function handleReadFileRequest(
|
|
349
175
|
req: Request,
|
|
350
|
-
corsHeaders: Record<string, string
|
|
176
|
+
corsHeaders: Record<string, string>,
|
|
177
|
+
sessionManager: SessionManager
|
|
351
178
|
): Promise<Response> {
|
|
352
179
|
try {
|
|
353
180
|
const body = (await req.json()) as ReadFileRequest;
|
|
354
181
|
const { path, encoding = "utf-8", sessionId } = body;
|
|
355
182
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}),
|
|
361
|
-
{
|
|
362
|
-
headers: {
|
|
363
|
-
"Content-Type": "application/json",
|
|
364
|
-
...corsHeaders,
|
|
365
|
-
},
|
|
366
|
-
status: 400,
|
|
367
|
-
}
|
|
368
|
-
);
|
|
183
|
+
// Validate path
|
|
184
|
+
const pathError = validatePath(path);
|
|
185
|
+
if (pathError) {
|
|
186
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
369
187
|
}
|
|
370
188
|
|
|
371
|
-
|
|
372
|
-
const dangerousPatterns = [
|
|
373
|
-
/^\/$/, // Root directory
|
|
374
|
-
/^\/etc/, // System directories
|
|
375
|
-
/^\/var/, // System directories
|
|
376
|
-
/^\/usr/, // System directories
|
|
377
|
-
/^\/bin/, // System directories
|
|
378
|
-
/^\/sbin/, // System directories
|
|
379
|
-
/^\/boot/, // System directories
|
|
380
|
-
/^\/dev/, // System directories
|
|
381
|
-
/^\/proc/, // System directories
|
|
382
|
-
/^\/sys/, // System directories
|
|
383
|
-
/^\/tmp\/\.\./, // Path traversal attempts
|
|
384
|
-
/\.\./, // Path traversal attempts
|
|
385
|
-
];
|
|
189
|
+
console.log(`[Server] Reading file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
386
190
|
|
|
387
|
-
if
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
"Content-Type": "application/json",
|
|
395
|
-
...corsHeaders,
|
|
396
|
-
},
|
|
397
|
-
status: 400,
|
|
398
|
-
}
|
|
399
|
-
);
|
|
191
|
+
// Use specific session if provided, otherwise default session
|
|
192
|
+
const result = sessionId
|
|
193
|
+
? await sessionManager.getSession(sessionId)?.readFileOperation(path, encoding)
|
|
194
|
+
: await sessionManager.readFile(path, encoding);
|
|
195
|
+
|
|
196
|
+
if (!result) {
|
|
197
|
+
return createServerErrorResponse("handleReadFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
400
198
|
}
|
|
401
199
|
|
|
402
|
-
console.log(`[Server] Reading file: ${path}`);
|
|
403
|
-
|
|
404
|
-
const result = await executeReadFile(path, encoding, sessionId);
|
|
405
|
-
|
|
406
200
|
return new Response(
|
|
407
201
|
JSON.stringify({
|
|
408
202
|
content: result.content,
|
|
@@ -419,108 +213,36 @@ export async function handleReadFileRequest(
|
|
|
419
213
|
}
|
|
420
214
|
);
|
|
421
215
|
} catch (error) {
|
|
422
|
-
|
|
423
|
-
return new Response(
|
|
424
|
-
JSON.stringify({
|
|
425
|
-
error: "Failed to read file",
|
|
426
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
427
|
-
}),
|
|
428
|
-
{
|
|
429
|
-
headers: {
|
|
430
|
-
"Content-Type": "application/json",
|
|
431
|
-
...corsHeaders,
|
|
432
|
-
},
|
|
433
|
-
status: 500,
|
|
434
|
-
}
|
|
435
|
-
);
|
|
216
|
+
return createServerErrorResponse("handleReadFileRequest", error, corsHeaders);
|
|
436
217
|
}
|
|
437
218
|
}
|
|
438
219
|
|
|
439
|
-
|
|
440
|
-
function executeDeleteFile(
|
|
441
|
-
path: string,
|
|
442
|
-
sessionId?: string
|
|
443
|
-
): Promise<{
|
|
444
|
-
success: boolean;
|
|
445
|
-
exitCode: number;
|
|
446
|
-
}> {
|
|
447
|
-
return new Promise((resolve, reject) => {
|
|
448
|
-
(async () => {
|
|
449
|
-
try {
|
|
450
|
-
// Delete the file
|
|
451
|
-
await unlink(path);
|
|
452
|
-
|
|
453
|
-
console.log(`[Server] File deleted successfully: ${path}`);
|
|
454
|
-
resolve({
|
|
455
|
-
exitCode: 0,
|
|
456
|
-
success: true,
|
|
457
|
-
});
|
|
458
|
-
} catch (error) {
|
|
459
|
-
console.error(`[Server] Error deleting file: ${path}`, error);
|
|
460
|
-
reject(error);
|
|
461
|
-
}
|
|
462
|
-
})();
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
|
|
466
220
|
export async function handleDeleteFileRequest(
|
|
467
221
|
req: Request,
|
|
468
|
-
corsHeaders: Record<string, string
|
|
222
|
+
corsHeaders: Record<string, string>,
|
|
223
|
+
sessionManager: SessionManager
|
|
469
224
|
): Promise<Response> {
|
|
470
225
|
try {
|
|
471
226
|
const body = (await req.json()) as DeleteFileRequest;
|
|
472
227
|
const { path, sessionId } = body;
|
|
473
228
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}),
|
|
479
|
-
{
|
|
480
|
-
headers: {
|
|
481
|
-
"Content-Type": "application/json",
|
|
482
|
-
...corsHeaders,
|
|
483
|
-
},
|
|
484
|
-
status: 400,
|
|
485
|
-
}
|
|
486
|
-
);
|
|
229
|
+
// Validate path
|
|
230
|
+
const pathError = validatePath(path);
|
|
231
|
+
if (pathError) {
|
|
232
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
487
233
|
}
|
|
488
234
|
|
|
489
|
-
|
|
490
|
-
const dangerousPatterns = [
|
|
491
|
-
/^\/$/, // Root directory
|
|
492
|
-
/^\/etc/, // System directories
|
|
493
|
-
/^\/var/, // System directories
|
|
494
|
-
/^\/usr/, // System directories
|
|
495
|
-
/^\/bin/, // System directories
|
|
496
|
-
/^\/sbin/, // System directories
|
|
497
|
-
/^\/boot/, // System directories
|
|
498
|
-
/^\/dev/, // System directories
|
|
499
|
-
/^\/proc/, // System directories
|
|
500
|
-
/^\/sys/, // System directories
|
|
501
|
-
/^\/tmp\/\.\./, // Path traversal attempts
|
|
502
|
-
/\.\./, // Path traversal attempts
|
|
503
|
-
];
|
|
235
|
+
console.log(`[Server] Deleting file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
504
236
|
|
|
505
|
-
if
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
"Content-Type": "application/json",
|
|
513
|
-
...corsHeaders,
|
|
514
|
-
},
|
|
515
|
-
status: 400,
|
|
516
|
-
}
|
|
517
|
-
);
|
|
237
|
+
// Use specific session if provided, otherwise default session
|
|
238
|
+
const result = sessionId
|
|
239
|
+
? await sessionManager.getSession(sessionId)?.deleteFileOperation(path)
|
|
240
|
+
: await sessionManager.deleteFile(path);
|
|
241
|
+
|
|
242
|
+
if (!result) {
|
|
243
|
+
return createServerErrorResponse("handleDeleteFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
518
244
|
}
|
|
519
245
|
|
|
520
|
-
console.log(`[Server] Deleting file: ${path}`);
|
|
521
|
-
|
|
522
|
-
const result = await executeDeleteFile(path, sessionId);
|
|
523
|
-
|
|
524
246
|
return new Response(
|
|
525
247
|
JSON.stringify({
|
|
526
248
|
exitCode: result.exitCode,
|
|
@@ -536,133 +258,36 @@ export async function handleDeleteFileRequest(
|
|
|
536
258
|
}
|
|
537
259
|
);
|
|
538
260
|
} catch (error) {
|
|
539
|
-
|
|
540
|
-
return new Response(
|
|
541
|
-
JSON.stringify({
|
|
542
|
-
error: "Failed to delete file",
|
|
543
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
544
|
-
}),
|
|
545
|
-
{
|
|
546
|
-
headers: {
|
|
547
|
-
"Content-Type": "application/json",
|
|
548
|
-
...corsHeaders,
|
|
549
|
-
},
|
|
550
|
-
status: 500,
|
|
551
|
-
}
|
|
552
|
-
);
|
|
261
|
+
return createServerErrorResponse("handleDeleteFileRequest", error, corsHeaders);
|
|
553
262
|
}
|
|
554
263
|
}
|
|
555
264
|
|
|
556
|
-
|
|
557
|
-
function executeRenameFile(
|
|
558
|
-
oldPath: string,
|
|
559
|
-
newPath: string,
|
|
560
|
-
sessionId?: string
|
|
561
|
-
): Promise<{
|
|
562
|
-
success: boolean;
|
|
563
|
-
exitCode: number;
|
|
564
|
-
}> {
|
|
565
|
-
return new Promise((resolve, reject) => {
|
|
566
|
-
(async () => {
|
|
567
|
-
try {
|
|
568
|
-
// Rename the file
|
|
569
|
-
await rename(oldPath, newPath);
|
|
570
|
-
|
|
571
|
-
console.log(
|
|
572
|
-
`[Server] File renamed successfully: ${oldPath} -> ${newPath}`
|
|
573
|
-
);
|
|
574
|
-
resolve({
|
|
575
|
-
exitCode: 0,
|
|
576
|
-
success: true,
|
|
577
|
-
});
|
|
578
|
-
} catch (error) {
|
|
579
|
-
console.error(
|
|
580
|
-
`[Server] Error renaming file: ${oldPath} -> ${newPath}`,
|
|
581
|
-
error
|
|
582
|
-
);
|
|
583
|
-
reject(error);
|
|
584
|
-
}
|
|
585
|
-
})();
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
|
|
589
265
|
export async function handleRenameFileRequest(
|
|
590
266
|
req: Request,
|
|
591
|
-
corsHeaders: Record<string, string
|
|
267
|
+
corsHeaders: Record<string, string>,
|
|
268
|
+
sessionManager: SessionManager
|
|
592
269
|
): Promise<Response> {
|
|
593
270
|
try {
|
|
594
271
|
const body = (await req.json()) as RenameFileRequest;
|
|
595
272
|
const { oldPath, newPath, sessionId } = body;
|
|
596
273
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}),
|
|
602
|
-
{
|
|
603
|
-
headers: {
|
|
604
|
-
"Content-Type": "application/json",
|
|
605
|
-
...corsHeaders,
|
|
606
|
-
},
|
|
607
|
-
status: 400,
|
|
608
|
-
}
|
|
609
|
-
);
|
|
274
|
+
// Validate paths
|
|
275
|
+
const pathError = validatePath(oldPath, newPath);
|
|
276
|
+
if (pathError) {
|
|
277
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
610
278
|
}
|
|
611
279
|
|
|
612
|
-
|
|
613
|
-
return new Response(
|
|
614
|
-
JSON.stringify({
|
|
615
|
-
error: "New path is required and must be a string",
|
|
616
|
-
}),
|
|
617
|
-
{
|
|
618
|
-
headers: {
|
|
619
|
-
"Content-Type": "application/json",
|
|
620
|
-
...corsHeaders,
|
|
621
|
-
},
|
|
622
|
-
status: 400,
|
|
623
|
-
}
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// Basic safety check - prevent dangerous paths
|
|
628
|
-
const dangerousPatterns = [
|
|
629
|
-
/^\/$/, // Root directory
|
|
630
|
-
/^\/etc/, // System directories
|
|
631
|
-
/^\/var/, // System directories
|
|
632
|
-
/^\/usr/, // System directories
|
|
633
|
-
/^\/bin/, // System directories
|
|
634
|
-
/^\/sbin/, // System directories
|
|
635
|
-
/^\/boot/, // System directories
|
|
636
|
-
/^\/dev/, // System directories
|
|
637
|
-
/^\/proc/, // System directories
|
|
638
|
-
/^\/sys/, // System directories
|
|
639
|
-
/^\/tmp\/\.\./, // Path traversal attempts
|
|
640
|
-
/\.\./, // Path traversal attempts
|
|
641
|
-
];
|
|
280
|
+
console.log(`[Server] Renaming file: ${oldPath} -> ${newPath}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
642
281
|
|
|
643
|
-
if
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
)
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
error: "Dangerous path not allowed",
|
|
651
|
-
}),
|
|
652
|
-
{
|
|
653
|
-
headers: {
|
|
654
|
-
"Content-Type": "application/json",
|
|
655
|
-
...corsHeaders,
|
|
656
|
-
},
|
|
657
|
-
status: 400,
|
|
658
|
-
}
|
|
659
|
-
);
|
|
282
|
+
// Use specific session if provided, otherwise default session
|
|
283
|
+
const result = sessionId
|
|
284
|
+
? await sessionManager.getSession(sessionId)?.renameFileOperation(oldPath, newPath)
|
|
285
|
+
: await sessionManager.renameFile(oldPath, newPath);
|
|
286
|
+
|
|
287
|
+
if (!result) {
|
|
288
|
+
return createServerErrorResponse("handleRenameFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
660
289
|
}
|
|
661
290
|
|
|
662
|
-
console.log(`[Server] Renaming file: ${oldPath} -> ${newPath}`);
|
|
663
|
-
|
|
664
|
-
const result = await executeRenameFile(oldPath, newPath, sessionId);
|
|
665
|
-
|
|
666
291
|
return new Response(
|
|
667
292
|
JSON.stringify({
|
|
668
293
|
exitCode: result.exitCode,
|
|
@@ -679,137 +304,36 @@ export async function handleRenameFileRequest(
|
|
|
679
304
|
}
|
|
680
305
|
);
|
|
681
306
|
} catch (error) {
|
|
682
|
-
|
|
683
|
-
return new Response(
|
|
684
|
-
JSON.stringify({
|
|
685
|
-
error: "Failed to rename file",
|
|
686
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
687
|
-
}),
|
|
688
|
-
{
|
|
689
|
-
headers: {
|
|
690
|
-
"Content-Type": "application/json",
|
|
691
|
-
...corsHeaders,
|
|
692
|
-
},
|
|
693
|
-
status: 500,
|
|
694
|
-
}
|
|
695
|
-
);
|
|
307
|
+
return createServerErrorResponse("handleRenameFileRequest", error, corsHeaders);
|
|
696
308
|
}
|
|
697
309
|
}
|
|
698
310
|
|
|
699
|
-
|
|
700
|
-
function executeMoveFile(
|
|
701
|
-
sourcePath: string,
|
|
702
|
-
destinationPath: string,
|
|
703
|
-
sessionId?: string
|
|
704
|
-
): Promise<{
|
|
705
|
-
success: boolean;
|
|
706
|
-
exitCode: number;
|
|
707
|
-
}> {
|
|
708
|
-
return new Promise((resolve, reject) => {
|
|
709
|
-
(async () => {
|
|
710
|
-
try {
|
|
711
|
-
// Move the file
|
|
712
|
-
await rename(sourcePath, destinationPath);
|
|
713
|
-
|
|
714
|
-
console.log(
|
|
715
|
-
`[Server] File moved successfully: ${sourcePath} -> ${destinationPath}`
|
|
716
|
-
);
|
|
717
|
-
resolve({
|
|
718
|
-
exitCode: 0,
|
|
719
|
-
success: true,
|
|
720
|
-
});
|
|
721
|
-
} catch (error) {
|
|
722
|
-
console.error(
|
|
723
|
-
`[Server] Error moving file: ${sourcePath} -> ${destinationPath}`,
|
|
724
|
-
error
|
|
725
|
-
);
|
|
726
|
-
reject(error);
|
|
727
|
-
}
|
|
728
|
-
})();
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
|
|
732
311
|
export async function handleMoveFileRequest(
|
|
733
312
|
req: Request,
|
|
734
|
-
corsHeaders: Record<string, string
|
|
313
|
+
corsHeaders: Record<string, string>,
|
|
314
|
+
sessionManager: SessionManager
|
|
735
315
|
): Promise<Response> {
|
|
736
316
|
try {
|
|
737
317
|
const body = (await req.json()) as MoveFileRequest;
|
|
738
318
|
const { sourcePath, destinationPath, sessionId } = body;
|
|
739
319
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}),
|
|
745
|
-
{
|
|
746
|
-
headers: {
|
|
747
|
-
"Content-Type": "application/json",
|
|
748
|
-
...corsHeaders,
|
|
749
|
-
},
|
|
750
|
-
status: 400,
|
|
751
|
-
}
|
|
752
|
-
);
|
|
320
|
+
// Validate paths
|
|
321
|
+
const pathError = validatePath(sourcePath, destinationPath);
|
|
322
|
+
if (pathError) {
|
|
323
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
753
324
|
}
|
|
754
325
|
|
|
755
|
-
|
|
756
|
-
return new Response(
|
|
757
|
-
JSON.stringify({
|
|
758
|
-
error: "Destination path is required and must be a string",
|
|
759
|
-
}),
|
|
760
|
-
{
|
|
761
|
-
headers: {
|
|
762
|
-
"Content-Type": "application/json",
|
|
763
|
-
...corsHeaders,
|
|
764
|
-
},
|
|
765
|
-
status: 400,
|
|
766
|
-
}
|
|
767
|
-
);
|
|
768
|
-
}
|
|
326
|
+
console.log(`[Server] Moving file: ${sourcePath} -> ${destinationPath}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
769
327
|
|
|
770
|
-
//
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
/^\/sbin/, // System directories
|
|
778
|
-
/^\/boot/, // System directories
|
|
779
|
-
/^\/dev/, // System directories
|
|
780
|
-
/^\/proc/, // System directories
|
|
781
|
-
/^\/sys/, // System directories
|
|
782
|
-
/^\/tmp\/\.\./, // Path traversal attempts
|
|
783
|
-
/\.\./, // Path traversal attempts
|
|
784
|
-
];
|
|
785
|
-
|
|
786
|
-
if (
|
|
787
|
-
dangerousPatterns.some(
|
|
788
|
-
(pattern) => pattern.test(sourcePath) || pattern.test(destinationPath)
|
|
789
|
-
)
|
|
790
|
-
) {
|
|
791
|
-
return new Response(
|
|
792
|
-
JSON.stringify({
|
|
793
|
-
error: "Dangerous path not allowed",
|
|
794
|
-
}),
|
|
795
|
-
{
|
|
796
|
-
headers: {
|
|
797
|
-
"Content-Type": "application/json",
|
|
798
|
-
...corsHeaders,
|
|
799
|
-
},
|
|
800
|
-
status: 400,
|
|
801
|
-
}
|
|
802
|
-
);
|
|
328
|
+
// Use specific session if provided, otherwise default session
|
|
329
|
+
const result = sessionId
|
|
330
|
+
? await sessionManager.getSession(sessionId)?.moveFileOperation(sourcePath, destinationPath)
|
|
331
|
+
: await sessionManager.moveFile(sourcePath, destinationPath);
|
|
332
|
+
|
|
333
|
+
if (!result) {
|
|
334
|
+
return createServerErrorResponse("handleMoveFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
803
335
|
}
|
|
804
336
|
|
|
805
|
-
console.log(`[Server] Moving file: ${sourcePath} -> ${destinationPath}`);
|
|
806
|
-
|
|
807
|
-
const result = await executeMoveFile(
|
|
808
|
-
sourcePath,
|
|
809
|
-
destinationPath,
|
|
810
|
-
sessionId
|
|
811
|
-
);
|
|
812
|
-
|
|
813
337
|
return new Response(
|
|
814
338
|
JSON.stringify({
|
|
815
339
|
destinationPath,
|
|
@@ -826,208 +350,40 @@ export async function handleMoveFileRequest(
|
|
|
826
350
|
}
|
|
827
351
|
);
|
|
828
352
|
} catch (error) {
|
|
829
|
-
|
|
830
|
-
return new Response(
|
|
831
|
-
JSON.stringify({
|
|
832
|
-
error: "Failed to move file",
|
|
833
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
834
|
-
}),
|
|
835
|
-
{
|
|
836
|
-
headers: {
|
|
837
|
-
"Content-Type": "application/json",
|
|
838
|
-
...corsHeaders,
|
|
839
|
-
},
|
|
840
|
-
status: 500,
|
|
841
|
-
}
|
|
842
|
-
);
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
async function executeListFiles(
|
|
848
|
-
path: string,
|
|
849
|
-
options?: {
|
|
850
|
-
recursive?: boolean;
|
|
851
|
-
includeHidden?: boolean;
|
|
852
|
-
}
|
|
853
|
-
): Promise<{
|
|
854
|
-
success: boolean;
|
|
855
|
-
exitCode: number;
|
|
856
|
-
files: Array<{
|
|
857
|
-
name: string;
|
|
858
|
-
absolutePath: string;
|
|
859
|
-
relativePath: string;
|
|
860
|
-
type: 'file' | 'directory' | 'symlink' | 'other';
|
|
861
|
-
size: number;
|
|
862
|
-
modifiedAt: string;
|
|
863
|
-
mode: string;
|
|
864
|
-
permissions: {
|
|
865
|
-
readable: boolean;
|
|
866
|
-
writable: boolean;
|
|
867
|
-
executable: boolean;
|
|
868
|
-
};
|
|
869
|
-
}>;
|
|
870
|
-
}> {
|
|
871
|
-
try {
|
|
872
|
-
const basePath = path.endsWith('/') ? path.slice(0, -1) : path;
|
|
873
|
-
const files: Array<{
|
|
874
|
-
name: string;
|
|
875
|
-
absolutePath: string;
|
|
876
|
-
relativePath: string;
|
|
877
|
-
type: 'file' | 'directory' | 'symlink' | 'other';
|
|
878
|
-
size: number;
|
|
879
|
-
modifiedAt: string;
|
|
880
|
-
mode: string;
|
|
881
|
-
permissions: {
|
|
882
|
-
readable: boolean;
|
|
883
|
-
writable: boolean;
|
|
884
|
-
executable: boolean;
|
|
885
|
-
};
|
|
886
|
-
}> = [];
|
|
887
|
-
|
|
888
|
-
// Helper function to convert numeric mode to string like "rwxr-xr-x"
|
|
889
|
-
function modeToString(mode: number): string {
|
|
890
|
-
const perms = ['---', '--x', '-w-', '-wx', 'r--', 'r-x', 'rw-', 'rwx'];
|
|
891
|
-
const user = (mode >> 6) & 7;
|
|
892
|
-
const group = (mode >> 3) & 7;
|
|
893
|
-
const other = mode & 7;
|
|
894
|
-
return perms[user] + perms[group] + perms[other];
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
// Helper function to extract permission booleans for current user
|
|
898
|
-
function getPermissions(mode: number): { readable: boolean; writable: boolean; executable: boolean } {
|
|
899
|
-
// Extract user permissions (owner permissions)
|
|
900
|
-
const userPerms = (mode >> 6) & 7;
|
|
901
|
-
return {
|
|
902
|
-
readable: (userPerms & 4) !== 0,
|
|
903
|
-
writable: (userPerms & 2) !== 0,
|
|
904
|
-
executable: (userPerms & 1) !== 0
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
async function scanDirectory(dirPath: string): Promise<void> {
|
|
909
|
-
const entries = await readdir(dirPath);
|
|
910
|
-
|
|
911
|
-
for (const entry of entries) {
|
|
912
|
-
// Skip hidden files unless includeHidden is true
|
|
913
|
-
if (!options?.includeHidden && entry.startsWith('.')) {
|
|
914
|
-
continue;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
const fullPath = join(dirPath, entry);
|
|
918
|
-
const stats = await stat(fullPath);
|
|
919
|
-
|
|
920
|
-
let type: 'file' | 'directory' | 'symlink' | 'other';
|
|
921
|
-
if (stats.isDirectory()) {
|
|
922
|
-
type = 'directory';
|
|
923
|
-
} else if (stats.isFile()) {
|
|
924
|
-
type = 'file';
|
|
925
|
-
} else if (stats.isSymbolicLink()) {
|
|
926
|
-
type = 'symlink';
|
|
927
|
-
} else {
|
|
928
|
-
type = 'other';
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
// Extract mode (permissions) - stats.mode is a number
|
|
932
|
-
const mode = stats.mode & 0o777; // Get only permission bits
|
|
933
|
-
|
|
934
|
-
// Calculate relative path from base directory
|
|
935
|
-
const relativePath = fullPath.startsWith(`${basePath}/`)
|
|
936
|
-
? fullPath.substring(basePath.length + 1)
|
|
937
|
-
: fullPath === basePath
|
|
938
|
-
? '.'
|
|
939
|
-
: entry;
|
|
940
|
-
|
|
941
|
-
const fileInfo = {
|
|
942
|
-
name: entry,
|
|
943
|
-
absolutePath: fullPath,
|
|
944
|
-
relativePath,
|
|
945
|
-
type,
|
|
946
|
-
size: stats.size,
|
|
947
|
-
modifiedAt: stats.mtime.toISOString(),
|
|
948
|
-
mode: modeToString(mode),
|
|
949
|
-
permissions: getPermissions(mode)
|
|
950
|
-
};
|
|
951
|
-
|
|
952
|
-
files.push(fileInfo);
|
|
953
|
-
|
|
954
|
-
// Recursively scan subdirectories if requested
|
|
955
|
-
if (options?.recursive && type === 'directory') {
|
|
956
|
-
await scanDirectory(fullPath);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
await scanDirectory(path);
|
|
962
|
-
|
|
963
|
-
console.log(`[Server] Listed ${files.length} files in: ${path}`);
|
|
964
|
-
return {
|
|
965
|
-
exitCode: 0,
|
|
966
|
-
files,
|
|
967
|
-
success: true,
|
|
968
|
-
};
|
|
969
|
-
} catch (error) {
|
|
970
|
-
console.error(`[Server] Error listing files in: ${path}`, error);
|
|
971
|
-
throw error;
|
|
353
|
+
return createServerErrorResponse("handleMoveFileRequest", error, corsHeaders);
|
|
972
354
|
}
|
|
973
355
|
}
|
|
974
356
|
|
|
975
357
|
export async function handleListFilesRequest(
|
|
976
358
|
req: Request,
|
|
977
|
-
corsHeaders: Record<string, string
|
|
359
|
+
corsHeaders: Record<string, string>,
|
|
360
|
+
sessionManager: SessionManager
|
|
978
361
|
): Promise<Response> {
|
|
979
362
|
try {
|
|
980
363
|
const body = (await req.json()) as ListFilesRequest;
|
|
981
|
-
const { path, options } = body;
|
|
364
|
+
const { path, options, sessionId } = body;
|
|
982
365
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}),
|
|
988
|
-
{
|
|
989
|
-
headers: {
|
|
990
|
-
"Content-Type": "application/json",
|
|
991
|
-
...corsHeaders,
|
|
992
|
-
},
|
|
993
|
-
status: 400,
|
|
994
|
-
}
|
|
995
|
-
);
|
|
366
|
+
// Validate path (note: listFiles allows root directory listing)
|
|
367
|
+
const pathError = validatePath(path);
|
|
368
|
+
if (pathError && pathError !== "Dangerous path not allowed") {
|
|
369
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
996
370
|
}
|
|
997
|
-
|
|
998
|
-
//
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
/^\/var/, // System directories
|
|
1002
|
-
/^\/usr/, // System directories
|
|
1003
|
-
/^\/bin/, // System directories
|
|
1004
|
-
/^\/sbin/, // System directories
|
|
1005
|
-
/^\/boot/, // System directories
|
|
1006
|
-
/^\/dev/, // System directories
|
|
1007
|
-
/^\/proc/, // System directories
|
|
1008
|
-
/^\/sys/, // System directories
|
|
1009
|
-
/^\/tmp\/\.\./, // Path traversal attempts
|
|
1010
|
-
/\.\./, // Path traversal attempts
|
|
1011
|
-
];
|
|
1012
|
-
|
|
1013
|
-
if (dangerousPatterns.some((pattern) => pattern.test(path))) {
|
|
1014
|
-
return new Response(
|
|
1015
|
-
JSON.stringify({
|
|
1016
|
-
error: "Dangerous path not allowed",
|
|
1017
|
-
}),
|
|
1018
|
-
{
|
|
1019
|
-
headers: {
|
|
1020
|
-
"Content-Type": "application/json",
|
|
1021
|
-
...corsHeaders,
|
|
1022
|
-
},
|
|
1023
|
-
status: 400,
|
|
1024
|
-
}
|
|
1025
|
-
);
|
|
371
|
+
|
|
372
|
+
// For listFiles, we allow root directory but still check other dangerous patterns
|
|
373
|
+
if (path !== "/" && DANGEROUS_PATH_PATTERNS.slice(1).some((pattern) => pattern.test(path))) {
|
|
374
|
+
return createPathErrorResponse("Dangerous path not allowed", corsHeaders);
|
|
1026
375
|
}
|
|
1027
376
|
|
|
1028
|
-
console.log(`[Server] Listing files in: ${path}`);
|
|
377
|
+
console.log(`[Server] Listing files in: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
1029
378
|
|
|
1030
|
-
|
|
379
|
+
// Use specific session if provided, otherwise default session
|
|
380
|
+
const result = sessionId
|
|
381
|
+
? await sessionManager.getSession(sessionId)?.listFilesOperation(path, options)
|
|
382
|
+
: await sessionManager.listFiles(path, options);
|
|
383
|
+
|
|
384
|
+
if (!result) {
|
|
385
|
+
return createServerErrorResponse("handleListFilesRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
386
|
+
}
|
|
1031
387
|
|
|
1032
388
|
return new Response(
|
|
1033
389
|
JSON.stringify({
|
|
@@ -1045,20 +401,6 @@ export async function handleListFilesRequest(
|
|
|
1045
401
|
}
|
|
1046
402
|
);
|
|
1047
403
|
} catch (error) {
|
|
1048
|
-
|
|
1049
|
-
return new Response(
|
|
1050
|
-
JSON.stringify({
|
|
1051
|
-
error: "Failed to list files",
|
|
1052
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
1053
|
-
}),
|
|
1054
|
-
{
|
|
1055
|
-
headers: {
|
|
1056
|
-
"Content-Type": "application/json",
|
|
1057
|
-
...corsHeaders,
|
|
1058
|
-
},
|
|
1059
|
-
status: 500,
|
|
1060
|
-
}
|
|
1061
|
-
);
|
|
404
|
+
return createServerErrorResponse("handleListFilesRequest", error, corsHeaders);
|
|
1062
405
|
}
|
|
1063
|
-
}
|
|
1064
|
-
|
|
406
|
+
}
|