mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-13 17:54:19 +01:00
fix: PDF Timestamp bug
This commit is contained in:
1 parent
6f5c75b913
commit
ea53689bba
27 files changed
+201
-59
No files matched your search
@@ -153,8 +153,21 @@ async function generateProxySignature(
|
||||
.join('');
|
||||
}
|
||||
|
||||
function resolveProxyBase(): string {
|
||||
if (
|
||||
CORS_PROXY_URL.startsWith('http://') ||
|
||||
CORS_PROXY_URL.startsWith('https://')
|
||||
) {
|
||||
return CORS_PROXY_URL;
|
||||
}
|
||||
if (typeof window !== 'undefined' && window.location?.origin) {
|
||||
return new URL(CORS_PROXY_URL, window.location.origin).toString();
|
||||
}
|
||||
return CORS_PROXY_URL;
|
||||
}
|
||||
|
||||
async function buildCorsProxyUrl(url: string): Promise<string> {
|
||||
let proxyUrl = `${CORS_PROXY_URL}?url=${encodeURIComponent(url)}`;
|
||||
let proxyUrl = `${resolveProxyBase()}?url=${encodeURIComponent(url)}`;
|
||||
|
||||
if (!CORS_PROXY_SECRET) {
|
||||
return proxyUrl;
|
||||
@@ -196,6 +209,9 @@ function createCorsAwareFetch(): {
|
||||
? input.toString()
|
||||
: input.url;
|
||||
|
||||
const isAlreadyProxied =
|
||||
CORS_PROXY_URL.length > 0 && url.startsWith(CORS_PROXY_URL);
|
||||
|
||||
const isExternalCertificateUrl =
|
||||
(url.includes('.crt') ||
|
||||
url.includes('.cer') ||
|
||||
@@ -221,6 +237,7 @@ function createCorsAwareFetch(): {
|
||||
url.includes('RFC3161');
|
||||
|
||||
const shouldProxy =
|
||||
!isAlreadyProxied &&
|
||||
(isExternalCertificateUrl || isTsaRequest) &&
|
||||
!url.startsWith(window.location.origin);
|
||||
|
||||
@@ -332,6 +349,19 @@ export async function timestampPdf(
|
||||
pdfBytes: Uint8Array,
|
||||
tsaUrl: string
|
||||
): Promise<Uint8Array> {
|
||||
const pageIsHttps =
|
||||
typeof window !== 'undefined' && window.location?.protocol === 'https:';
|
||||
const tsaIsHttp = /^http:\/\//i.test(tsaUrl);
|
||||
|
||||
if (pageIsHttps && tsaIsHttp && !CORS_PROXY_URL) {
|
||||
throw new Error(
|
||||
`This TSA endpoint uses HTTP (${tsaUrl}). The browser blocks insecure ` +
|
||||
`requests from this HTTPS page. Either choose a TSA with an HTTPS ` +
|
||||
`endpoint or configure VITE_CORS_PROXY_URL at build time so the ` +
|
||||
`request can be relayed through your proxy.`
|
||||
);
|
||||
}
|
||||
|
||||
let effectiveUrl = tsaUrl;
|
||||
|
||||
if (CORS_PROXY_URL) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { t } from '../i18n/i18n';
|
||||
import {
|
||||
readFileAsArrayBuffer,
|
||||
formatBytes,
|
||||
@@ -197,13 +198,7 @@ function updateProcessButton(): void {
|
||||
const processBtn = getElement<HTMLButtonElement>('process-btn');
|
||||
if (!processBtn) return;
|
||||
|
||||
if (state.pdfBytes) {
|
||||
processBtn.classList.remove('hidden');
|
||||
processBtn.disabled = false;
|
||||
} else {
|
||||
processBtn.classList.add('hidden');
|
||||
processBtn.disabled = true;
|
||||
}
|
||||
processBtn.disabled = !state.pdfBytes;
|
||||
}
|
||||
|
||||
function getTsaUrl(): string | null {
|
||||
@@ -233,8 +228,8 @@ async function processTimestamp(): Promise<void> {
|
||||
downloadFile(blob, state.pdfFile.name);
|
||||
|
||||
showAlert(
|
||||
'Success',
|
||||
'PDF timestamped successfully! The timestamp can be verified in Adobe Acrobat and other PDF readers.',
|
||||
t('common.success'),
|
||||
t('tools:timestampPdf.successMessage'),
|
||||
'success'
|
||||
);
|
||||
|
||||
|
||||
@@ -194,41 +194,41 @@
|
||||
class="w-full bg-gray-700 border border-gray-600 text-gray-200 rounded-lg px-3 py-2 focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="process-btn"
|
||||
class="hidden w-full mt-6 bg-indigo-600 hover:bg-indigo-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white font-semibold py-3 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2"
|
||||
disabled
|
||||
>
|
||||
<i data-lucide="clock" class="w-5 h-5"></i>
|
||||
<span data-i18n="tools:timestampPdf.applyTimestamp"
|
||||
>Apply Timestamp</span
|
||||
<button
|
||||
id="process-btn"
|
||||
class="btn-gradient w-full mt-6 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled
|
||||
data-i18n="tools:timestampPdf.applyTimestamp"
|
||||
>
|
||||
</button>
|
||||
Apply Timestamp
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loader Modal -->
|
||||
<div
|
||||
id="loader-overlay"
|
||||
class="hidden fixed inset-0 bg-black/60 z-50 flex items-center justify-center"
|
||||
id="loader-modal"
|
||||
class="hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50"
|
||||
>
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="inline-block animate-spin rounded-full h-12 w-12 border-4 border-indigo-400 border-t-transparent mb-4"
|
||||
></div>
|
||||
<div
|
||||
class="bg-gray-800 p-8 rounded-lg flex flex-col items-center gap-4 border border-gray-700 shadow-xl"
|
||||
>
|
||||
<div class="solid-spinner"></div>
|
||||
<p id="loader-text" class="text-white text-lg font-medium">
|
||||
Processing...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alert Modal -->
|
||||
<div
|
||||
id="alert-overlay"
|
||||
class="hidden fixed inset-0 bg-black/60 z-50 flex items-center justify-center"
|
||||
id="alert-modal"
|
||||
class="fixed inset-0 bg-gray-900 bg-opacity-90 flex items-center justify-center z-50 hidden"
|
||||
>
|
||||
<div
|
||||
class="bg-gray-800 rounded-xl shadow-xl p-6 max-w-md w-full mx-4 border border-gray-700"
|
||||
class="bg-gray-800 rounded-lg shadow-xl p-6 max-w-sm w-full border border-gray-700"
|
||||
>
|
||||
<h3
|
||||
id="alert-title"
|
||||
|
||||
@@ -115,4 +115,42 @@ describe('timestampPdf', () => {
|
||||
|
||||
expect(samplePdfBytes).toEqual(originalCopy);
|
||||
});
|
||||
|
||||
it('should pre-proxy the TSA URL when CORS proxy is configured', async () => {
|
||||
vi.stubEnv(
|
||||
'VITE_CORS_PROXY_URL',
|
||||
'https://bentopdf-cors-proxy.bentopdf.workers.dev'
|
||||
);
|
||||
vi.resetModules();
|
||||
const { timestampPdf: freshTimestamp } =
|
||||
await import('@/js/logic/digital-sign-pdf');
|
||||
|
||||
mockSign.mockResolvedValueOnce(new Uint8Array([1]));
|
||||
await freshTimestamp(samplePdfBytes, 'http://timestamp.digicert.com');
|
||||
|
||||
const callArg = vi.mocked(PdfSigner).mock.calls[0][0] as {
|
||||
signdate: { url: string };
|
||||
};
|
||||
expect(callArg.signdate.url).toMatch(
|
||||
/^https:\/\/bentopdf-cors-proxy\.bentopdf\.workers\.dev\?url=/
|
||||
);
|
||||
expect(callArg.signdate.url).toContain(
|
||||
encodeURIComponent('http://timestamp.digicert.com')
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw a clear error when HTTPS page targets HTTP TSA without proxy', async () => {
|
||||
vi.stubEnv('VITE_CORS_PROXY_URL', '');
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
value: { protocol: 'https:', origin: 'https://www.bentopdf.com' },
|
||||
});
|
||||
vi.resetModules();
|
||||
const { timestampPdf: freshTimestamp } =
|
||||
await import('@/js/logic/digital-sign-pdf');
|
||||
|
||||
await expect(
|
||||
freshTimestamp(samplePdfBytes, 'http://timestamp.digicert.com')
|
||||
).rejects.toThrow(/HTTPS page|VITE_CORS_PROXY_URL/);
|
||||
});
|
||||
});
|
||||
@@ -149,12 +149,14 @@ describe('TimestampNode', () => {
|
||||
expect(result.pdf[0].filename).toBe('report_timestamped.pdf');
|
||||
});
|
||||
|
||||
it('should wrap errors from timestampPdf with TSA context', async () => {
|
||||
vi.mocked(timestampPdf).mockRejectedValueOnce(new Error('Network error'));
|
||||
it('should wrap errors from timestampPdf with TSA context and original cause', async () => {
|
||||
const originalError = new Error('Network error');
|
||||
vi.mocked(timestampPdf).mockRejectedValueOnce(originalError);
|
||||
const node = new TimestampNode();
|
||||
|
||||
await expect(
|
||||
node.data({
|
||||
let caught: Error | null = null;
|
||||
try {
|
||||
await node.data({
|
||||
pdf: [
|
||||
{
|
||||
type: 'pdf' as const,
|
||||
@@ -163,7 +165,12 @@ describe('TimestampNode', () => {
|
||||
filename: 'test.pdf',
|
||||
},
|
||||
],
|
||||
})
|
||||
).rejects.toThrow(/Failed to timestamp using TSA/);
|
||||
});
|
||||
} catch (err) {
|
||||
caught = err as Error;
|
||||
}
|
||||
|
||||
expect(caught).toBeInstanceOf(Error);
|
||||
expect(caught?.cause).toBe(originalError);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user