XrefParser.js 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. const { NoXrefTableError } = require('./Errors')
  2. const fs = require('fs')
  3. const { O_RDONLY, O_NOFOLLOW } = fs.constants
  4. const MAX_XREF_FILE_SIZE = 1024 * 1024
  5. /** Parse qpdf --show-xref output to get a table of xref entries
  6. *
  7. * @param {string} filePath
  8. * @param {number} pdfFileSize
  9. * @returns
  10. */
  11. async function parseXrefTable(filePath, pdfFileSize) {
  12. try {
  13. // the xref table will be written to output.pdfxref when available
  14. const xRefFilePath = filePath + 'xref'
  15. // check the size of the file (as it is untrusted)
  16. const stats = await fs.promises.stat(xRefFilePath)
  17. if (!stats.isFile()) {
  18. throw new NoXrefTableError('xref file invalid type')
  19. }
  20. if (stats.size === 0) {
  21. throw new NoXrefTableError('xref file empty')
  22. }
  23. if (stats.size > MAX_XREF_FILE_SIZE) {
  24. throw new NoXrefTableError('xref file too large')
  25. }
  26. const content = await fs.promises.readFile(xRefFilePath, {
  27. encoding: 'ascii',
  28. flag: O_RDONLY | O_NOFOLLOW,
  29. })
  30. // the qpdf xref table output looks like this:
  31. //
  32. // 3/0: uncompressed; offset = 194159
  33. //
  34. // we only need the uncompressed objects
  35. const matches = content.matchAll(
  36. // put an upper limit of 10^10 on all the matched numbers for safety
  37. // ignore the generation id in "id/gen"
  38. // in a linearized pdf all objects must have generation number 0
  39. /^\d{1,9}\/\d{1,9}: uncompressed; offset = (\d{1,9})$/gm
  40. )
  41. // include a zero-index object for backwards compatibility with
  42. // our existing xref table parsing code
  43. const xRefEntries = [{ offset: 0 }]
  44. // extract all the xref table entries
  45. for (const match of matches) {
  46. const offset = parseInt(match[1], 10)
  47. xRefEntries.push({ offset, uncompressed: true })
  48. }
  49. if (xRefEntries.length === 1) {
  50. throw new NoXrefTableError('xref file has no objects')
  51. }
  52. return { xRefEntries }
  53. } catch (err) {
  54. if (err instanceof NoXrefTableError) {
  55. throw err
  56. } else if (err.code) {
  57. throw new NoXrefTableError(`xref file error ${err.code}`)
  58. } else {
  59. throw new NoXrefTableError('xref file parse error')
  60. }
  61. }
  62. }
  63. module.exports = {
  64. parseXrefTable,
  65. }