- inklingMedia.ts:39 no-op replace 제거 + 명료한 host+pathname 결합 코멘트 - inbox:open-media 빈 relPath 명시적 거절 (typeof + length 검사) - NoteCard <img> alt="" decorative 의도 코멘트 472/472 + typecheck 0 유지. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
import electron from 'electron';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { join, normalize, sep, extname } from 'node:path';
|
|
|
|
const { protocol } = electron;
|
|
|
|
export function registerSchemesAsPrivileged(): void {
|
|
protocol.registerSchemesAsPrivileged([
|
|
{ scheme: 'inkling-media', privileges: { secure: true, supportFetchAPI: true, stream: true } }
|
|
]);
|
|
}
|
|
|
|
export function inferMime(filename: string): string {
|
|
const ext = extname(filename).toLowerCase();
|
|
switch (ext) {
|
|
case '.png': return 'image/png';
|
|
case '.jpg':
|
|
case '.jpeg': return 'image/jpeg';
|
|
case '.gif': return 'image/gif';
|
|
case '.webp': return 'image/webp';
|
|
default: return 'application/octet-stream';
|
|
}
|
|
}
|
|
|
|
export function registerInklingMediaProtocol(profileDir: string): void {
|
|
const mediaRoot = join(profileDir, 'media');
|
|
protocol.handle('inkling-media', async (req) => {
|
|
// Raw URL 에서 `..` 세그먼트 (URL-encoded 포함) 검출 — `new URL()` 이 normalize 하기 전에 차단.
|
|
const rawLower = req.url.toLowerCase();
|
|
if (
|
|
rawLower.includes('/../') ||
|
|
rawLower.endsWith('/..') ||
|
|
rawLower.includes('/%2e%2e/') ||
|
|
rawLower.endsWith('/%2e%2e')
|
|
) {
|
|
return new Response(null, { status: 403 });
|
|
}
|
|
const url = new URL(req.url);
|
|
// inkling-media://media/<noteId>/<file> → host='media', pathname='/<noteId>/<file>'
|
|
const relPath = decodeURIComponent(`${url.host}${url.pathname}`);
|
|
const target = normalize(join(profileDir, relPath));
|
|
if (!target.startsWith(mediaRoot + sep) && target !== mediaRoot) {
|
|
return new Response(null, { status: 403 });
|
|
}
|
|
try {
|
|
const data = await readFile(target);
|
|
return new Response(new Uint8Array(data), {
|
|
headers: { 'content-type': inferMime(target) }
|
|
});
|
|
} catch {
|
|
return new Response(null, { status: 404 });
|
|
}
|
|
});
|
|
}
|