mirror of
https://github.com/pdfme/pdfme.git
synced 2026-06-16 10:19:20 -04:00
* Migrate pdf-lib into pdfme monorepo - Add @pdfme/pdf-lib package to packages/ directory - Update root package.json to include pdf-lib in workspaces - Update all package dependencies to use workspace:* for @pdfme/pdf-lib - Configure TypeScript build targets (cjs, esm, node) for pdf-lib - Add ESLint configuration with relaxed rules for pdf-lib migration - Integrate pdf-lib into monorepo build and clean scripts - Add basic test suite for pdf-lib package - All lint, build, and test suites pass successfully This migration improves maintainability by consolidating all PDF operations into a single repository and unified build/test/release process. Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com> * Fix TypeScript module resolution for workspace dependencies - Changed moduleResolution from 'bundler' to 'node' in common package - This should resolve '@pdfme/pdf-lib' module resolution issues - Reverted workspace dependency format back to '*' for npm compatibility Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com> * Fix pdf-lib package.json exports paths - Updated main, module, and exports paths to point to correct locations - Changed from dist/*/index.js to dist/*/src/index.js to match build output - Fixed TypeScript types path from dist/types/index.d.ts to dist/types/src/index.d.ts - Resolves Vite package entry resolution errors and TypeScript module resolution issues Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com> * Fix CodeQL security alerts in svg.ts - Add input validation and sanitization for HTML/SVG parsing - Prevent ReDoS attacks with regex limits and input size checks - Sanitize font family names to prevent prototype pollution - Add URL validation for image sources to prevent path traversal - Limit transformation parsing to prevent infinite loops - Maintain backward compatibility while improving security Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com> * Implement comprehensive security fixes for CodeQL alerts in svg.ts - Add input validation and sanitization for SVG content - Implement safe HTML parsing with null checks and size limits - Add controlled dynamic property access with allowlisted tag names - Prevent style injection with filtered and limited style entries - Add regex match limits to prevent ReDoS attacks - Enhance font selection with input validation and type safety - Sanitize image sources to prevent path traversal and injection - Limit CSS style parsing to prevent potential vulnerabilities These changes address the 2 high-severity CodeQL security alerts while maintaining backward compatibility and functionality. Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com> * Add additional security fixes for CodeQL alerts in svg.ts - Implement safer property access for polygon node transformation - Add input validation for points attribute with regex pattern matching - Replace Object.assign with safer property assignment to prevent prototype pollution - Add null checks and type validation for node attributes and childNodes - Implement safer SVG node parsing with comprehensive validation - Add array type checks for childNodes processing These changes target the remaining 2 high-severity CodeQL security alerts by addressing potential prototype pollution and unsafe property access. Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com> * Implement comprehensive security hardening for CodeQL alerts in svg.ts - Add comprehensive SVG content sanitization with allowlist-based tag filtering - Implement strict input validation with bounds checking for all numeric inputs - Replace unsafe dynamic property assignment with Object.defineProperty - Add try-catch error handling for HTML parsing operations - Restrict allowed style properties and validate string lengths - Use setAttribute/removeAttribute instead of direct attribute manipulation - Add type safety checks for all node operations - Implement safer polygon-to-path conversion with validation These changes address the 10 high-severity CodeQL security alerts by: 1. Preventing XSS through comprehensive input sanitization 2. Avoiding prototype pollution with safer property assignment 3. Adding bounds checking to prevent DoS attacks 4. Using allowlist-based validation for all user inputs 5. Implementing proper error handling to prevent crashes Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com> * Potential fix for code scanning alert no. 32: Incomplete multi-character sanitization Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 39: Incomplete multi-character sanitization Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Fix inefficient regular expression in svg.ts to pass CodeQL - Changed /([^:\s]+)*\s*:\s*([^;]+)/g to /([^:\s]+)\s*:\s*([^;]+)/g - Removed the problematic * quantifier that could cause exponential backtracking - This fixes the "Inefficient regular expression" security alert from GitHub Advanced Security 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * remove sanitize-html * move tests * fix for security * update dependabot.yml * organize --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Kyohei Fukuda <kyouhei.fukuda0729@gmail.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
152 lines
6.8 KiB
TypeScript
152 lines
6.8 KiB
TypeScript
import fs from 'fs';
|
|
import { PDFArray, PDFDocument, PDFName, StandardFonts } from '../../src/index';
|
|
|
|
const birdPng = fs.readFileSync('assets/images/greyscale_bird.png');
|
|
|
|
describe(`PDFDocument`, () => {
|
|
describe(`getSize() method`, () => {
|
|
it(`returns the width and height of the the page's MediaBox`, async () => {
|
|
const pdfDoc = await PDFDocument.create();
|
|
const page = pdfDoc.addPage();
|
|
page.node.set(PDFName.MediaBox, pdfDoc.context.obj([5, 5, 20, 50]));
|
|
expect(page.getSize()).toEqual({ width: 15, height: 45 });
|
|
});
|
|
});
|
|
|
|
describe(`setSize() method`, () => {
|
|
it(`sets the width and height of only the the page's MediaBox when the other boxes are not defined`, async () => {
|
|
const pdfDoc = await PDFDocument.create();
|
|
const page = pdfDoc.addPage();
|
|
|
|
page.setMediaBox(5, 5, 20, 50);
|
|
expect(page.getMediaBox()).toEqual({ x: 5, y: 5, width: 20, height: 50 });
|
|
expect(page.node.MediaBox()).toBeInstanceOf(PDFArray);
|
|
expect(page.node.CropBox()).toBeUndefined();
|
|
expect(page.node.BleedBox()).toBeUndefined();
|
|
expect(page.node.TrimBox()).toBeUndefined();
|
|
expect(page.node.ArtBox()).toBeUndefined();
|
|
|
|
page.setSize(15, 45);
|
|
expect(page.getSize()).toEqual({ width: 15, height: 45 });
|
|
expect(page.getMediaBox()).toEqual({ x: 5, y: 5, width: 15, height: 45 });
|
|
expect(page.node.MediaBox()).toBeInstanceOf(PDFArray);
|
|
expect(page.node.CropBox()).toBeUndefined();
|
|
expect(page.node.BleedBox()).toBeUndefined();
|
|
expect(page.node.TrimBox()).toBeUndefined();
|
|
expect(page.node.ArtBox()).toBeUndefined();
|
|
});
|
|
|
|
it(`sets the width and height of the the page's CropBox, BleedBox, TrimBox, and ArtBox when they equal the MediaBox`, async () => {
|
|
const pdfDoc = await PDFDocument.create();
|
|
const page = pdfDoc.addPage();
|
|
|
|
page.setMediaBox(5, 5, 20, 50);
|
|
page.setCropBox(5, 5, 20, 50);
|
|
page.setBleedBox(5, 5, 20, 50);
|
|
page.setTrimBox(5, 5, 20, 50);
|
|
page.setArtBox(5, 5, 20, 50);
|
|
|
|
expect(page.getMediaBox()).toEqual({ x: 5, y: 5, width: 20, height: 50 });
|
|
expect(page.getCropBox()).toEqual({ x: 5, y: 5, width: 20, height: 50 });
|
|
expect(page.getBleedBox()).toEqual({ x: 5, y: 5, width: 20, height: 50 });
|
|
expect(page.getTrimBox()).toEqual({ x: 5, y: 5, width: 20, height: 50 });
|
|
expect(page.getArtBox()).toEqual({ x: 5, y: 5, width: 20, height: 50 });
|
|
|
|
expect(page.node.MediaBox()).toBeInstanceOf(PDFArray);
|
|
expect(page.node.CropBox()).toBeInstanceOf(PDFArray);
|
|
expect(page.node.BleedBox()).toBeInstanceOf(PDFArray);
|
|
expect(page.node.TrimBox()).toBeInstanceOf(PDFArray);
|
|
expect(page.node.ArtBox()).toBeInstanceOf(PDFArray);
|
|
|
|
page.setSize(15, 45);
|
|
expect(page.getSize()).toEqual({ width: 15, height: 45 });
|
|
expect(page.getMediaBox()).toEqual({ x: 5, y: 5, width: 15, height: 45 });
|
|
expect(page.getCropBox()).toEqual({ x: 5, y: 5, width: 15, height: 45 });
|
|
expect(page.getBleedBox()).toEqual({ x: 5, y: 5, width: 15, height: 45 });
|
|
expect(page.getTrimBox()).toEqual({ x: 5, y: 5, width: 15, height: 45 });
|
|
expect(page.getArtBox()).toEqual({ x: 5, y: 5, width: 15, height: 45 });
|
|
});
|
|
|
|
it(`does not set the width and height of the the page's CropBox, BleedBox, TrimBox, or ArtBox when they do not equal the MediaBox`, async () => {
|
|
const pdfDoc = await PDFDocument.create();
|
|
const page = pdfDoc.addPage();
|
|
|
|
page.setMediaBox(5, 5, 20, 50);
|
|
page.setCropBox(0, 0, 20, 50);
|
|
page.setBleedBox(5, 5, 10, 25);
|
|
page.setTrimBox(5, 0, 10, 50);
|
|
page.setArtBox(0, 5, 20, 25);
|
|
|
|
expect(page.getMediaBox()).toEqual({ x: 5, y: 5, width: 20, height: 50 });
|
|
expect(page.getCropBox()).toEqual({ x: 0, y: 0, width: 20, height: 50 });
|
|
expect(page.getBleedBox()).toEqual({ x: 5, y: 5, width: 10, height: 25 });
|
|
expect(page.getTrimBox()).toEqual({ x: 5, y: 0, width: 10, height: 50 });
|
|
expect(page.getArtBox()).toEqual({ x: 0, y: 5, width: 20, height: 25 });
|
|
|
|
expect(page.node.MediaBox()).toBeInstanceOf(PDFArray);
|
|
expect(page.node.CropBox()).toBeInstanceOf(PDFArray);
|
|
expect(page.node.BleedBox()).toBeInstanceOf(PDFArray);
|
|
expect(page.node.TrimBox()).toBeInstanceOf(PDFArray);
|
|
expect(page.node.ArtBox()).toBeInstanceOf(PDFArray);
|
|
|
|
page.setSize(15, 45);
|
|
expect(page.getSize()).toEqual({ width: 15, height: 45 });
|
|
expect(page.getMediaBox()).toEqual({ x: 5, y: 5, width: 15, height: 45 });
|
|
expect(page.getCropBox()).toEqual({ x: 0, y: 0, width: 20, height: 50 });
|
|
expect(page.getBleedBox()).toEqual({ x: 5, y: 5, width: 10, height: 25 });
|
|
expect(page.getTrimBox()).toEqual({ x: 5, y: 0, width: 10, height: 50 });
|
|
expect(page.getArtBox()).toEqual({ x: 0, y: 5, width: 20, height: 25 });
|
|
});
|
|
});
|
|
|
|
// https://github.com/Hopding/pdf-lib/issues/1075
|
|
it(`drawImage() does not reuse existing XObject keys`, async () => {
|
|
const pdfDoc1 = await PDFDocument.create();
|
|
const image1 = await pdfDoc1.embedPng(birdPng);
|
|
const page1 = pdfDoc1.addPage();
|
|
|
|
expect(page1.node.normalizedEntries().XObject.keys().length).toEqual(0);
|
|
page1.drawImage(image1);
|
|
expect(page1.node.normalizedEntries().XObject.keys().length).toEqual(1);
|
|
|
|
const key1 = page1.node.normalizedEntries().XObject.keys()[0];
|
|
|
|
const pdfDoc2 = await PDFDocument.load(await pdfDoc1.save());
|
|
const image2 = await pdfDoc2.embedPng(birdPng);
|
|
const page2 = pdfDoc2.getPage(0);
|
|
|
|
expect(page2.node.normalizedEntries().XObject.keys().length).toEqual(1);
|
|
page2.drawImage(image2);
|
|
expect(page2.node.normalizedEntries().XObject.keys().length).toEqual(2);
|
|
|
|
const key2 = page2.node.normalizedEntries().XObject.keys()[1];
|
|
expect(key1).not.toEqual(key2);
|
|
expect(page2.node.normalizedEntries().XObject.keys()).toEqual([key1, key2]);
|
|
});
|
|
|
|
// https://github.com/Hopding/pdf-lib/issues/1075
|
|
it(`setFont() does not reuse existing Font keys`, async () => {
|
|
const pdfDoc1 = await PDFDocument.create();
|
|
const font1 = await pdfDoc1.embedFont(StandardFonts.Helvetica);
|
|
const page1 = pdfDoc1.addPage();
|
|
|
|
expect(page1.node.normalizedEntries().Font.keys().length).toEqual(0);
|
|
page1.setFont(font1);
|
|
expect(page1.node.normalizedEntries().Font.keys().length).toEqual(1);
|
|
|
|
const key1 = page1.node.normalizedEntries().Font.keys()[0];
|
|
|
|
const pdfDoc2 = await PDFDocument.load(await pdfDoc1.save());
|
|
const font2 = await pdfDoc2.embedFont(StandardFonts.Helvetica);
|
|
const page2 = pdfDoc2.getPage(0);
|
|
|
|
expect(page2.node.normalizedEntries().Font.keys().length).toEqual(1);
|
|
page2.setFont(font2);
|
|
expect(page2.node.normalizedEntries().Font.keys().length).toEqual(2);
|
|
|
|
const key2 = page2.node.normalizedEntries().Font.keys()[1];
|
|
expect(key1).not.toEqual(key2);
|
|
expect(page2.node.normalizedEntries().Font.keys()).toEqual([key1, key2]);
|
|
});
|
|
});
|