fix: allow genuine HTML file downloads
Address Codex review feedback: only reject HTML responses when the file metadata indicates a non-HTML type. Genuine HTML files (with mimetype text/html or .html extension) are now allowed through. - Check file.mimetype and file.name extension before rejecting HTML - Add tests for legitimate HTML file downloads
This commit is contained in:
parent
da6f3e21a5
commit
b305640e69
@ -289,6 +289,76 @@ describe("resolveSlackMedia", () => {
|
|||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("allows genuine HTML file when mimetype indicates text/html", async () => {
|
||||||
|
// Mock the store module
|
||||||
|
vi.doMock("../../media/store.js", () => ({
|
||||||
|
saveMediaBuffer: vi.fn().mockResolvedValue({
|
||||||
|
path: "/tmp/test.html",
|
||||||
|
contentType: "text/html",
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { resolveSlackMedia } = await import("./media.js");
|
||||||
|
|
||||||
|
// A genuine HTML file shared by a user
|
||||||
|
const htmlContent = `<!DOCTYPE html><html><body>User's HTML page</body></html>`;
|
||||||
|
const htmlResponse = new Response(htmlContent, {
|
||||||
|
status: 200,
|
||||||
|
headers: { "content-type": "text/html; charset=utf-8" },
|
||||||
|
});
|
||||||
|
mockFetch.mockResolvedValueOnce(htmlResponse);
|
||||||
|
|
||||||
|
const result = await resolveSlackMedia({
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
url_private: "https://files.slack.com/page.html",
|
||||||
|
name: "page.html",
|
||||||
|
mimetype: "text/html",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
token: "xoxb-test-token",
|
||||||
|
maxBytes: 10 * 1024 * 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should allow genuine HTML files
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
expect(result?.contentType).toBe("text/html");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows HTML file based on .html extension even without mimetype", async () => {
|
||||||
|
// Mock the store module
|
||||||
|
vi.doMock("../../media/store.js", () => ({
|
||||||
|
saveMediaBuffer: vi.fn().mockResolvedValue({
|
||||||
|
path: "/tmp/test.html",
|
||||||
|
contentType: "text/html",
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { resolveSlackMedia } = await import("./media.js");
|
||||||
|
|
||||||
|
const htmlContent = `<!DOCTYPE html><html><body>User's HTML snippet</body></html>`;
|
||||||
|
const htmlResponse = new Response(htmlContent, {
|
||||||
|
status: 200,
|
||||||
|
headers: { "content-type": "text/html" },
|
||||||
|
});
|
||||||
|
mockFetch.mockResolvedValueOnce(htmlResponse);
|
||||||
|
|
||||||
|
const result = await resolveSlackMedia({
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
url_private: "https://files.slack.com/snippet.html",
|
||||||
|
name: "snippet.html",
|
||||||
|
// No mimetype set
|
||||||
|
},
|
||||||
|
],
|
||||||
|
token: "xoxb-test-token",
|
||||||
|
maxBytes: 10 * 1024 * 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should allow based on .html extension
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("falls through to next file when first returns HTML", async () => {
|
it("falls through to next file when first returns HTML", async () => {
|
||||||
// Mock the store module
|
// Mock the store module
|
||||||
vi.doMock("../../media/store.js", () => ({
|
vi.doMock("../../media/store.js", () => ({
|
||||||
|
|||||||
@ -82,8 +82,12 @@ export async function resolveSlackMedia(params: {
|
|||||||
|
|
||||||
// Guard: reject if we received HTML instead of expected media.
|
// Guard: reject if we received HTML instead of expected media.
|
||||||
// This happens when Slack auth fails and returns a login page.
|
// This happens when Slack auth fails and returns a login page.
|
||||||
|
// Skip this check if the file metadata indicates it's genuinely an HTML file.
|
||||||
const detectedMime = fetched.contentType?.split(";")[0]?.trim().toLowerCase();
|
const detectedMime = fetched.contentType?.split(";")[0]?.trim().toLowerCase();
|
||||||
if (detectedMime === "text/html" || looksLikeHtml(fetched.buffer)) {
|
const expectedMime = file.mimetype?.split(";")[0]?.trim().toLowerCase();
|
||||||
|
const isExpectedHtml =
|
||||||
|
expectedMime === "text/html" || file.name?.toLowerCase().endsWith(".html");
|
||||||
|
if (!isExpectedHtml && (detectedMime === "text/html" || looksLikeHtml(fetched.buffer))) {
|
||||||
const fileId = file.name ?? file.id ?? "unknown";
|
const fileId = file.name ?? file.id ?? "unknown";
|
||||||
logWarn(
|
logWarn(
|
||||||
`slack: received HTML instead of media for file ${fileId}; possible auth failure or expired URL`,
|
`slack: received HTML instead of media for file ${fileId}; possible auth failure or expired URL`,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user