@hyperframes/studio-server 0.8.70 → 0.8.72
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/dist/index.js +43 -10
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -302,8 +302,10 @@ import { bodyLimit } from "hono/body-limit";
|
|
|
302
302
|
import {
|
|
303
303
|
closeSync,
|
|
304
304
|
existsSync as existsSync3,
|
|
305
|
+
lstatSync as lstatSync3,
|
|
305
306
|
openSync,
|
|
306
307
|
readFileSync as readFileSync5,
|
|
308
|
+
readlinkSync,
|
|
307
309
|
writeFileSync as writeFileSync4,
|
|
308
310
|
writeSync,
|
|
309
311
|
mkdirSync as mkdirSync3,
|
|
@@ -1020,6 +1022,24 @@ function resolveGsapWriter(env) {
|
|
|
1020
1022
|
async function loadGsapParser() {
|
|
1021
1023
|
return import("@hyperframes/parsers/gsap-parser-recast");
|
|
1022
1024
|
}
|
|
1025
|
+
function isDanglingSymlinkInProject(projectDir, lexicalPath) {
|
|
1026
|
+
if (!isSafePath(projectDir, dirname3(lexicalPath))) return false;
|
|
1027
|
+
let stats;
|
|
1028
|
+
try {
|
|
1029
|
+
stats = lstatSync3(lexicalPath);
|
|
1030
|
+
} catch {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
if (!stats.isSymbolicLink()) return false;
|
|
1034
|
+
const target = resolve3(dirname3(lexicalPath), readlinkSync(lexicalPath));
|
|
1035
|
+
if (!isSafePath(projectDir, target)) return false;
|
|
1036
|
+
try {
|
|
1037
|
+
statSync3(lexicalPath);
|
|
1038
|
+
return false;
|
|
1039
|
+
} catch {
|
|
1040
|
+
return true;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1023
1043
|
async function resolveProjectPath(c, adapter, pathPrefix, opts) {
|
|
1024
1044
|
const id = c.req.param("id");
|
|
1025
1045
|
const project = await adapter.resolveProject(id);
|
|
@@ -1037,6 +1057,9 @@ async function resolveProjectPath(c, adapter, pathPrefix, opts) {
|
|
|
1037
1057
|
}
|
|
1038
1058
|
const absPath = resolveWithinProject(project.dir, filePath);
|
|
1039
1059
|
if (!absPath) {
|
|
1060
|
+
if (isDanglingSymlinkInProject(project.dir, resolve3(project.dir, filePath))) {
|
|
1061
|
+
return { error: c.json({ error: "not found", why: "dangling_symlink" }, 404) };
|
|
1062
|
+
}
|
|
1040
1063
|
return { error: c.json({ error: "forbidden", why: "outside_project" }, 403) };
|
|
1041
1064
|
}
|
|
1042
1065
|
if (opts?.mustExist && !existsSync3(absPath)) {
|
|
@@ -2410,21 +2433,31 @@ function registerFileRoutes(api, adapter) {
|
|
|
2410
2433
|
api.get("/projects/:id/files/*", async (c) => {
|
|
2411
2434
|
const res = await resolveProjectFile(c, adapter);
|
|
2412
2435
|
if ("error" in res) return res.error;
|
|
2413
|
-
|
|
2436
|
+
let fd;
|
|
2437
|
+
try {
|
|
2438
|
+
fd = openSync(res.absPath, "r");
|
|
2439
|
+
} catch {
|
|
2414
2440
|
if (c.req.query("optional") === "1") {
|
|
2415
2441
|
return c.json({ filename: res.filePath, content: "", missing: true });
|
|
2416
2442
|
}
|
|
2417
2443
|
return c.json({ error: "not found" }, 404);
|
|
2418
2444
|
}
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
version
|
|
2426
|
-
|
|
2427
|
-
|
|
2445
|
+
try {
|
|
2446
|
+
if (!fstatSync(fd).isFile()) {
|
|
2447
|
+
return c.json({ error: "not found", why: "not_a_file" }, 404);
|
|
2448
|
+
}
|
|
2449
|
+
const content = readFileSync5(fd);
|
|
2450
|
+
const version = fileContentVersion(content);
|
|
2451
|
+
c.header("ETag", version);
|
|
2452
|
+
return c.json({
|
|
2453
|
+
filename: res.filePath,
|
|
2454
|
+
content: content.toString("utf-8"),
|
|
2455
|
+
version,
|
|
2456
|
+
missing: false
|
|
2457
|
+
});
|
|
2458
|
+
} finally {
|
|
2459
|
+
closeSync(fd);
|
|
2460
|
+
}
|
|
2428
2461
|
});
|
|
2429
2462
|
api.put("/projects/:id/files/*", async (c) => {
|
|
2430
2463
|
const res = await resolveProjectFile(c, adapter);
|