@electron-memory/monitor 0.2.1 → 0.2.3

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.mjs CHANGED
@@ -1073,9 +1073,10 @@ import { BrowserWindow as BrowserWindow2 } from "electron";
1073
1073
  import * as path3 from "path";
1074
1074
 
1075
1075
  // src/core/dashboard-protocol.ts
1076
- import { app as app2, protocol } from "electron";
1076
+ import { app as app2, net, protocol } from "electron";
1077
1077
  import { readFile } from "fs/promises";
1078
1078
  import * as path2 from "path";
1079
+ import { pathToFileURL } from "url";
1079
1080
  var SCHEME = "emm-dashboard";
1080
1081
  var privilegedRegistered = false;
1081
1082
  var handlerRegistered = false;
@@ -1104,8 +1105,60 @@ function isPathInsideRoot(filePath, root) {
1104
1105
  if (f === r) {
1105
1106
  return true;
1106
1107
  }
1107
- const sep2 = r.endsWith(path2.sep) ? r : `${r}${path2.sep}`;
1108
- return f.startsWith(sep2);
1108
+ const rel = path2.relative(r, f);
1109
+ if (!rel || rel.startsWith("..") || path2.isAbsolute(rel)) {
1110
+ return false;
1111
+ }
1112
+ return true;
1113
+ }
1114
+ function urlToUiRelativePath(requestUrl) {
1115
+ let u;
1116
+ try {
1117
+ u = new URL(requestUrl);
1118
+ } catch {
1119
+ return "";
1120
+ }
1121
+ let p = "";
1122
+ try {
1123
+ p = decodeURIComponent(u.pathname || "");
1124
+ } catch {
1125
+ p = u.pathname || "";
1126
+ }
1127
+ p = p.replace(/^\/+/, "");
1128
+ const host = (u.hostname || "").toLowerCase();
1129
+ if (p.includes("..")) {
1130
+ return "";
1131
+ }
1132
+ if (host === "electron" || host === "") {
1133
+ return p || "index.html";
1134
+ }
1135
+ if (p) {
1136
+ return `${host}/${p}`.replace(/\\/g, "/");
1137
+ }
1138
+ if (host.includes(".")) {
1139
+ return host;
1140
+ }
1141
+ return "index.html";
1142
+ }
1143
+ function bufferToResponseBody(buf) {
1144
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
1145
+ }
1146
+ function looksLikeHtml(buf) {
1147
+ let i = 0;
1148
+ if (buf.length >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {
1149
+ i = 3;
1150
+ }
1151
+ while (i < buf.length && (buf[i] === 32 || buf[i] === 9 || buf[i] === 10 || buf[i] === 13)) {
1152
+ i += 1;
1153
+ }
1154
+ return i < buf.length && buf[i] === 60;
1155
+ }
1156
+ function corsHeaders() {
1157
+ return {
1158
+ "Access-Control-Allow-Origin": "*",
1159
+ "Access-Control-Allow-Methods": "GET, OPTIONS",
1160
+ "Access-Control-Allow-Headers": "*"
1161
+ };
1109
1162
  }
1110
1163
  function registerDashboardSchemePrivileged() {
1111
1164
  if (privilegedRegistered) {
@@ -1137,29 +1190,55 @@ function ensureDashboardProtocolHandler(uiRoot) {
1137
1190
  handlerRegistered = true;
1138
1191
  const base = path2.resolve(uiRoot);
1139
1192
  protocol.handle(SCHEME, async (request) => {
1193
+ if (request.method === "OPTIONS") {
1194
+ return new Response(null, { status: 204, headers: corsHeaders() });
1195
+ }
1140
1196
  try {
1141
- const { pathname } = new URL(request.url);
1142
- let rel = pathname.startsWith("/") ? pathname.slice(1) : pathname;
1143
- if (!rel) {
1144
- rel = "index.html";
1197
+ const rel = urlToUiRelativePath(request.url);
1198
+ if (!rel || rel.includes("..")) {
1199
+ return new Response("Bad path", { status: 400, headers: corsHeaders() });
1145
1200
  }
1146
- const filePath = path2.resolve(path2.join(base, rel));
1201
+ const filePath = path2.resolve(path2.join(base, ...rel.split("/")));
1147
1202
  if (!isPathInsideRoot(filePath, base)) {
1148
- return new Response("Forbidden", { status: 403 });
1203
+ return new Response("Forbidden", { status: 403, headers: corsHeaders() });
1204
+ }
1205
+ const fileUrl = pathToFileURL(filePath).href;
1206
+ let upstream;
1207
+ try {
1208
+ upstream = await net.fetch(fileUrl);
1209
+ } catch {
1210
+ upstream = new Response(null, { status: 599 });
1211
+ }
1212
+ let buf;
1213
+ if (!upstream.ok) {
1214
+ buf = await readFile(filePath);
1215
+ } else {
1216
+ const ab = await upstream.arrayBuffer();
1217
+ buf = Buffer.from(ab);
1149
1218
  }
1150
- const body = await readFile(filePath);
1151
1219
  const ext = path2.extname(filePath).toLowerCase();
1220
+ if (ext === ".html" && !looksLikeHtml(buf)) {
1221
+ console.error(
1222
+ "[@electron-memory/monitor] emm-dashboard: not HTML at",
1223
+ filePath,
1224
+ "url=",
1225
+ request.url
1226
+ );
1227
+ return new Response("Invalid dashboard HTML", { status: 500, headers: corsHeaders() });
1228
+ }
1152
1229
  const mime = MIME_BY_EXT[ext] || "application/octet-stream";
1153
- return new Response(body, {
1230
+ return new Response(bufferToResponseBody(buf), {
1154
1231
  status: 200,
1155
1232
  headers: {
1156
1233
  "Content-Type": mime,
1157
- "Cache-Control": "no-store"
1234
+ "Cache-Control": "no-store",
1235
+ ...corsHeaders()
1158
1236
  }
1159
1237
  });
1160
1238
  } catch (err) {
1161
1239
  const msg = err instanceof Error ? err.message : String(err);
1162
- return new Response(msg, { status: 404 });
1240
+ console.error("[@electron-memory/monitor] emm-dashboard:", request.url, err);
1241
+ return new Response(msg, { status: 404, headers: corsHeaders() });
1163
1242
  }
1164
1243
  });
1165
1244
  }