herdr-plugin-amq 0.1.9 → 0.1.10
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/package.json +1 -1
- package/src/protocol.mjs +48 -16
package/package.json
CHANGED
package/src/protocol.mjs
CHANGED
|
@@ -83,17 +83,48 @@ function openNoFollow(filePath, flags, mode) {
|
|
|
83
83
|
return fs.openSync(filePath, flags | noFollow, mode);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function
|
|
86
|
+
function isDirectoryHandle(handle) {
|
|
87
|
+
return Boolean(handle && typeof handle === "object");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function closeDirectory(handle) {
|
|
91
|
+
if (!isDirectoryHandle(handle)) fs.closeSync(handle);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function descriptorPath(handle) {
|
|
95
|
+
if (isDirectoryHandle(handle)) return handle.path;
|
|
87
96
|
const base = process.platform === "darwin" ? "/dev/fd" : "/proc/self/fd";
|
|
88
|
-
return path.join(base, String(
|
|
97
|
+
return path.join(base, String(handle));
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
function openRelativeNoFollow(
|
|
100
|
+
function openRelativeNoFollow(dirHandle, name, flags, mode) {
|
|
92
101
|
if (!name || path.basename(name) !== name) throw new Error("Invalid relative Maildir name");
|
|
93
|
-
return openNoFollow(path.join(descriptorPath(
|
|
102
|
+
return openNoFollow(path.join(descriptorPath(dirHandle), name), flags, mode);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolveDarwinDirectory(dirPath) {
|
|
106
|
+
const resolved = path.resolve(dirPath);
|
|
107
|
+
const parsed = path.parse(resolved);
|
|
108
|
+
const segments = resolved.slice(parsed.root.length).split(path.sep).filter(Boolean);
|
|
109
|
+
const systemSymlinkRoots = new Set(["/private", "/tmp", "/var"]);
|
|
110
|
+
let current = parsed.root;
|
|
111
|
+
for (const segment of segments) {
|
|
112
|
+
current = path.join(current, segment);
|
|
113
|
+
const stat = fs.lstatSync(current);
|
|
114
|
+
if (stat.isSymbolicLink()) {
|
|
115
|
+
if (!systemSymlinkRoots.has(current)) throw new Error("Symlinked directory is not allowed");
|
|
116
|
+
current = fs.realpathSync(current);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (!stat.isDirectory()) throw new Error("Maildir path is not a directory");
|
|
120
|
+
}
|
|
121
|
+
const realPath = fs.realpathSync(current);
|
|
122
|
+
if (!fs.statSync(realPath).isDirectory()) throw new Error("Maildir path is not a directory");
|
|
123
|
+
return realPath;
|
|
94
124
|
}
|
|
95
125
|
|
|
96
126
|
function openDirectorySecure(dirPath) {
|
|
127
|
+
if (process.platform === "darwin") return { path: resolveDarwinDirectory(dirPath) };
|
|
97
128
|
const resolved = path.resolve(dirPath);
|
|
98
129
|
const parsed = path.parse(resolved);
|
|
99
130
|
const segments = resolved.slice(parsed.root.length).split(path.sep).filter(Boolean);
|
|
@@ -102,12 +133,12 @@ function openDirectorySecure(dirPath) {
|
|
|
102
133
|
try {
|
|
103
134
|
for (const segment of segments) {
|
|
104
135
|
const next = openRelativeNoFollow(current, segment, directoryFlag);
|
|
105
|
-
|
|
136
|
+
closeDirectory(current);
|
|
106
137
|
current = next;
|
|
107
138
|
}
|
|
108
139
|
return current;
|
|
109
140
|
} catch (error) {
|
|
110
|
-
|
|
141
|
+
closeDirectory(current);
|
|
111
142
|
throw error;
|
|
112
143
|
}
|
|
113
144
|
}
|
|
@@ -145,7 +176,7 @@ export function writeBoundedFileAtomic(filePath, content, maxBytes = MAX_MAILDIR
|
|
|
145
176
|
try {
|
|
146
177
|
writeFileAtomicBetweenDirectories(path.basename(filePath), dirFd, dirFd, content, maxBytes);
|
|
147
178
|
} finally {
|
|
148
|
-
|
|
179
|
+
closeDirectory(dirFd);
|
|
149
180
|
}
|
|
150
181
|
}
|
|
151
182
|
|
|
@@ -154,7 +185,7 @@ export function listMaildirMessageFiles(dirPath) {
|
|
|
154
185
|
try {
|
|
155
186
|
return fs.readdirSync(descriptorPath(dirFd));
|
|
156
187
|
} finally {
|
|
157
|
-
|
|
188
|
+
closeDirectory(dirFd);
|
|
158
189
|
}
|
|
159
190
|
}
|
|
160
191
|
|
|
@@ -176,8 +207,8 @@ export function moveMaildirMessage(amqRoot, handle, fromFolder, toFolder, fileNa
|
|
|
176
207
|
);
|
|
177
208
|
return path.join(toDir, fileName);
|
|
178
209
|
} finally {
|
|
179
|
-
|
|
180
|
-
if (toFd !== undefined)
|
|
210
|
+
closeDirectory(fromFd);
|
|
211
|
+
if (toFd !== undefined) closeDirectory(toFd);
|
|
181
212
|
}
|
|
182
213
|
}
|
|
183
214
|
|
|
@@ -206,7 +237,7 @@ export function readMaildirMessageFile(filePath, maxBytes = MAX_MAILDIR_FILE_BYT
|
|
|
206
237
|
return Buffer.concat(chunks, total).toString("utf8");
|
|
207
238
|
} finally {
|
|
208
239
|
if (fd !== undefined) fs.closeSync(fd);
|
|
209
|
-
|
|
240
|
+
closeDirectory(dirFd);
|
|
210
241
|
}
|
|
211
242
|
}
|
|
212
243
|
|
|
@@ -410,8 +441,8 @@ export function sendMaildirMessage(amqRoot, options = {}) {
|
|
|
410
441
|
newFd = openDirectorySecure(path.join(recipientDir, "inbox", "new"));
|
|
411
442
|
writeFileAtomicBetweenDirectories(`${msgId}.md`, tmpFd, newFd, fileText);
|
|
412
443
|
} finally {
|
|
413
|
-
|
|
414
|
-
if (newFd !== undefined)
|
|
444
|
+
closeDirectory(tmpFd);
|
|
445
|
+
if (newFd !== undefined) closeDirectory(newFd);
|
|
415
446
|
}
|
|
416
447
|
}
|
|
417
448
|
|
|
@@ -420,7 +451,7 @@ export function sendMaildirMessage(amqRoot, options = {}) {
|
|
|
420
451
|
try {
|
|
421
452
|
writeFileAtomicBetweenDirectories(`${msgId}.md`, sentFd, sentFd, fileText);
|
|
422
453
|
} finally {
|
|
423
|
-
|
|
454
|
+
closeDirectory(sentFd);
|
|
424
455
|
}
|
|
425
456
|
|
|
426
457
|
return {
|
|
@@ -449,8 +480,9 @@ export function markMaildirMessageRead(amqRoot, handle, msgId) {
|
|
|
449
480
|
if (!agentDir) return { ok: false, error: "Mailbox not found" };
|
|
450
481
|
const newPath = path.join(agentDir, "inbox", "new", `${msgId}.md`);
|
|
451
482
|
const curPath = path.join(agentDir, "inbox", "cur", `${msgId}.md`);
|
|
483
|
+
const logicalCurPath = path.join(amqRoot, "agents", handle, "inbox", "cur", `${msgId}.md`);
|
|
452
484
|
if (!fs.existsSync(newPath)) {
|
|
453
|
-
if (fs.existsSync(curPath)) return { ok: true, alreadyRead: true, id: msgId, filePath:
|
|
485
|
+
if (fs.existsSync(curPath)) return { ok: true, alreadyRead: true, id: msgId, filePath: logicalCurPath };
|
|
454
486
|
return { ok: false, error: "Message not found" };
|
|
455
487
|
}
|
|
456
488
|
|
|
@@ -464,7 +496,7 @@ export function markMaildirMessageRead(amqRoot, handle, msgId) {
|
|
|
464
496
|
|
|
465
497
|
const filePath = moveMaildirMessage(amqRoot, handle, "new", "cur", `${msgId}.md`);
|
|
466
498
|
if (!filePath) return { ok: false, error: "Message could not be marked read" };
|
|
467
|
-
return { ok: true, alreadyRead: false, id: msgId, filePath };
|
|
499
|
+
return { ok: true, alreadyRead: false, id: msgId, filePath: logicalCurPath };
|
|
468
500
|
} catch (error) {
|
|
469
501
|
return { ok: false, error: error.message };
|
|
470
502
|
}
|