parseBuffer ignores byteOffset - small PDFs read from the wrong memory

Abierto Apto para principiantes
#427 1 comentario 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
78/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Tranquilo
Stack tecnológico
javascript, node.js
Área
backend

Línea de trabajo

Empieza en pdfparser.js, en parseBuffer(), y luego sigue createParserStream() y su ruta de Buffer.concat. Ejecuta el repro.mjs proporcionado con Node 24 y confirma el comportamiento para Buffers agrupados con un byteOffset distinto de cero. Se considera terminado cuando el PDF pequeño se analiza correctamente mediante parseBuffer() y createParserStream(), mientras loadPDF() permanece sin cambios.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

I started seeing issues with Invalid XRef stream header while parsing PDF files. They are not loaded from the files but from S3 bucket.

All this happened when there was a rollout of node 24.18 which increased buffer size from 8kB to 64kB (by default).

After long investigation I was able to get full reproduction and understanding of the source of the issue. parseBuffer() assumes the Buffer it is given starts at offset 0 of its underlying ArrayBuffer. Node returns pooled Buffers (views into a shared 64 KB ArrayBuffer) for allocations under 32 KB, and that includes fs.readFileSync() of any file under 32 KB.

Environment
  • pdf2json 4.0.3
  • Node 24.19.0 (Buffer.poolSize is 65536)
Reproduction
npm i pdf2json
node repro.mjs
import fs from "node:fs";
import PDFParser from "pdf2json";

// Write a tiny valid PDF so this script is self-contained.
// Any PDF smaller than 32 KB reproduces it just as well.
const objs = [
  "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n",
  "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n",
  "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n",
  "4 0 obj\n<< /Length 44 >>\nstream\nBT /F1 12 Tf 20 100 Td (hello pdf2json) Tj ET\nendstream\nendobj\n",
  "5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n",
];
let pdf = "%PDF-1.4\n";
const offsets = [];
for (const o of objs) { offsets.push(pdf.length); pdf += o; }
const xref = pdf.length;
pdf += `xref\n0 ${objs.length + 1}\n0000000000 65535 f \n`;
for (const o of offsets) pdf += `${String(o).padStart(10, "0")} 00000 n \n`;
pdf += `trailer\n<< /Size ${objs.length + 1} /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF\n`;
fs.writeFileSync("tiny.pdf", Buffer.from(pdf, "latin1"));

// Uncomment this line and the script passes. It is the only change needed.
// Buffer.poolSize = 0;

// Read the file the ordinary way. No slicing, no views, no manual buffers.
const buffer = fs.readFileSync("tiny.pdf");

console.log("node             :", process.version);
console.log("file size        :", buffer.length);
console.log("buffer.byteOffset:", buffer.byteOffset);
console.log("buffer.buffer    :", buffer.buffer.byteLength, "bytes");

const parser = new PDFParser(null, true);
parser.on("pdfParser_dataReady", () =>
  console.log("\nparsed OK:", JSON.stringify(parser.getRawTextContent().split("\n")[0])));
parser.on("pdfParser_dataError", (e) =>
  console.log("\nfailed:", String(e?.parserError ?? e)));
parser.parseBuffer(buffer);

Actual output:

node             : v24.19.0
file size        : 589
buffer.byteOffset: 2400
buffer.buffer    : 65536 bytes

failed: Error: Error: Invalid XRef stream header

Uncommenting Buffer.poolSize = 0 is the only change needed to make the same file parse:

buffer.byteOffset: 0
buffer.buffer    : 589 bytes

parsed OK: "hello pdf2json"
Probably this is the source of issues

pdfparser.js, in parseBuffer():

let pdfBufferParse = pdfBuffer;
if (pdfBufferParse.buffer.byteLength !== pdfBufferParse.length) {
    pdfBufferParse = Buffer.from(pdfBufferParse.buffer, 0, pdfBufferParse.byteLength);
}

When the input is a view into a larger ArrayBuffer, this re-wraps it starting at offset 0 rather than at pdfBuffer.byteOffset, so the parser gets whatever else is in the pool at that position.

Also affects

createParserStream()

Piping the same tiny PDF into createParserStream() fails identically, since it calls parseBuffer(Buffer.concat(chunks)) and Buffer.concat returns a pooled buffer. loadPDF() with a file path is not affected.

Lenguaje dominante
Java
Estrellas
2.2k
Forks
396
Merge medio
9 d 19 h
PR fusionados (30 d)
3

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de modesty/pdf2json

Todos los issues de modesty/pdf2json

Issues similares

Más issues de Java

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.