From 55e9de341edce48d895c825b63b0503d8a7d339b Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Wed, 19 Mar 2025 20:41:29 +0800 Subject: [PATCH] feat: add PDF URL validation and content type check Signed-off-by: Yukai Huang --- public/js/extra.js | 40 +++++++++++++++++++++++++++++++++++----- public/js/utils.js | 13 +++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/public/js/extra.js b/public/js/extra.js index 6bbe65e4a..53fdc76b5 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -28,7 +28,7 @@ import './lib/renderer/lightbox' import { renderCSVPreview } from './lib/renderer/csvpreview' import { escapeAttrValue } from './render' -import { sanitizeUrl } from './utils' +import { sanitizeUrl, isPdfUrl } from './utils' import markdownit from 'markdown-it' import markdownitContainer from 'markdown-it-container' @@ -634,11 +634,41 @@ export function finishView (view) { const cleanUrl = sanitizeUrl(url) const inner = $('
') $(this).append(inner) - setTimeout(() => { - PDFObject.embed(cleanUrl, inner, { - height: '400px' + + // First check URL format + const isPDFByExtension = /\.pdf(\?.*)?$/i.test(cleanUrl) || cleanUrl.includes('pdf') + + if (isPDFByExtension) { + // Show loading message while we check content type + const loadingMessage = $('
Verifying PDF file...
') + inner.html(loadingMessage) + + // Perform additional validation with HEAD request + isPdfUrl(cleanUrl).then(isPDFByContentType => { + if (isPDFByContentType) { + // Valid PDF by content type, embed it + PDFObject.embed(cleanUrl, inner, { + height: '400px' + }) + } else { + // URL format looks like PDF but content type doesn't match + inner.html('
The URL looks like a PDF but the server didn\'t confirm it has a PDF content type.
') + console.warn('URL has PDF extension but content type is not application/pdf:', cleanUrl) + + // Try to embed anyway as a fallback + setTimeout(() => { + PDFObject.embed(cleanUrl, inner, { + height: '400px', + fallbackLink: 'This doesn\'t appear to be a valid PDF. Click here to try downloading it directly.' + }) + }, 1) + } }) - }, 1) + } else { + // Not a valid PDF URL by extension + inner.html('
Invalid PDF URL. The URL must point to a PDF file.
') + console.warn('Invalid PDF URL format:', cleanUrl) + } }) // syntax highlighting view.find('code.raw').removeClass('raw') diff --git a/public/js/utils.js b/public/js/utils.js index 29ef262a3..3ef3cfd48 100644 --- a/public/js/utils.js +++ b/public/js/utils.js @@ -1,3 +1,4 @@ +/* global fetch */ import base64url from 'base64url' const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i @@ -46,3 +47,15 @@ export function sanitizeUrl (rawUrl) { return 'about:blank' } } + +// Check if URL is a PDF based on Content-Type header +export async function isPdfUrl (url) { + try { + const response = await fetch(url, { method: 'HEAD' }) + const contentType = response.headers.get('Content-Type') + return contentType === 'application/pdf' + } catch (error) { + console.warn('Error checking PDF content type:', error) + return false + } +}