Prevent circular references in the /Pages tree

This commit is contained in:
Jonas Jenwald 2020-02-08 17:43:53 +01:00
parent e2b30e9e9c
commit 3c7b7be100
7 changed files with 155 additions and 8 deletions

View File

@ -731,6 +731,7 @@ class Catalog {
getPageDict(pageIndex) { getPageDict(pageIndex) {
const capability = createPromiseCapability(); const capability = createPromiseCapability();
const nodesToVisit = [this.catDict.getRaw("Pages")]; const nodesToVisit = [this.catDict.getRaw("Pages")];
const visitedNodes = new RefSet();
const xref = this.xref, const xref = this.xref,
pageKidsCountCache = this.pageKidsCountCache; pageKidsCountCache = this.pageKidsCountCache;
let count, let count,
@ -747,6 +748,14 @@ class Catalog {
currentPageIndex += count; currentPageIndex += count;
continue; continue;
} }
// Prevent circular references in the /Pages tree.
if (visitedNodes.has(currentNode)) {
capability.reject(
new FormatError("Pages tree contains circular reference.")
);
return;
}
visitedNodes.put(currentNode);
xref.fetchAsync(currentNode).then(function(obj) { xref.fetchAsync(currentNode).then(function(obj) {
if (isDict(obj, "Page") || (isDict(obj) && !obj.has("Kids"))) { if (isDict(obj, "Page") || (isDict(obj) && !obj.has("Kids"))) {

View File

@ -117,6 +117,7 @@
!bug1132849.pdf !bug1132849.pdf
!issue6894.pdf !issue6894.pdf
!issue5804.pdf !issue5804.pdf
!Pages-tree-refs.pdf
!ShowText-ShadingPattern.pdf !ShowText-ShadingPattern.pdf
!complex_ttf_font.pdf !complex_ttf_font.pdf
!issue3694_reduced.pdf !issue3694_reduced.pdf

View File

@ -0,0 +1,84 @@
%PDF-1.7
1 0 obj
<< /Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<< /Type /Pages
/Kids [6 0 R 3 0 R]
/Count 2
/MediaBox [0 0 595 842]
>>
endobj
3 0 obj
<< /Type /Pages
/Kids [4 0 R]
/Count 1
/MediaBox [0 0 595 842]
>>
endobj
4 0 obj
<< /Type /Pages
/Kids [5 0 R]
/Count 1
/MediaBox [0 0 595 842]
>>
endobj
5 0 obj
<< /Type /Pages
/Kids [3 0 R]
/Count 1
/MediaBox [0 0 595 842]
>>
endobj
6 0 obj
<< /Type /Page
/Parent 2 0 R
/Resources
<< /Font
<< /F1
<< /Type /Font
/Subtype /Type1
/BaseFont /Courier
>>
>>
>>
/Contents [7 0 R]
>>
endobj
7 0 obj
<< /Length 69 >>
stream
BT
/F1 22 Tf
30 800 Td
(Testcase: 'Pages loop') Tj
ET
endstream
endobj
xref
0 8
0000000000 65535 f
0000000010 00000 n
0000000069 00000 n
0000000176 00000 n
0000000277 00000 n
0000000378 00000 n
0000000479 00000 n
0000000744 00000 n
trailer
<< /Root 1 0 R
/Size 8
>>
startxref
866
%%EOF

View File

@ -546,6 +546,43 @@ describe("api", function() {
}) })
.catch(done.fail); .catch(done.fail);
}); });
it("gets page, from /Pages tree with circular reference", function(done) {
const loadingTask = getDocument(
buildGetDocumentParams("Pages-tree-refs.pdf")
);
const page1 = loadingTask.promise.then(function(pdfDoc) {
return pdfDoc.getPage(1).then(
function(pdfPage) {
expect(pdfPage instanceof PDFPageProxy).toEqual(true);
expect(pdfPage.ref).toEqual({ num: 6, gen: 0 });
},
function(reason) {
throw new Error("shall not fail for valid page");
}
);
});
const page2 = loadingTask.promise.then(function(pdfDoc) {
return pdfDoc.getPage(2).then(
function(pdfPage) {
throw new Error("shall fail for invalid page");
},
function(reason) {
expect(reason instanceof Error).toEqual(true);
expect(reason.message).toEqual(
"Pages tree contains circular reference."
);
}
);
});
Promise.all([page1, page2]).then(function() {
loadingTask.destroy().then(done);
}, done.fail);
});
it("gets page index", function(done) { it("gets page index", function(done) {
// reference to second page // reference to second page
var ref = { num: 17, gen: 0 }; var ref = { num: 17, gen: 0 };

View File

@ -403,16 +403,20 @@ class PDFPageView {
console.error("Must be in new state before drawing"); console.error("Must be in new state before drawing");
this.reset(); // Ensure that we reset all state to prevent issues. this.reset(); // Ensure that we reset all state to prevent issues.
} }
const { div, pdfPage } = this;
if (!this.pdfPage) { if (!pdfPage) {
this.renderingState = RenderingStates.FINISHED; this.renderingState = RenderingStates.FINISHED;
return Promise.reject(new Error("Page is not loaded"));
if (this.loadingIconDiv) {
div.removeChild(this.loadingIconDiv);
delete this.loadingIconDiv;
}
return Promise.reject(new Error("pdfPage is not loaded"));
} }
this.renderingState = RenderingStates.RUNNING; this.renderingState = RenderingStates.RUNNING;
const pdfPage = this.pdfPage;
const div = this.div;
// Wrap the canvas so that if it has a CSS transform for high DPI the // Wrap the canvas so that if it has a CSS transform for high DPI the
// overflow will be hidden in Firefox. // overflow will be hidden in Firefox.
const canvasWrapper = document.createElement("div"); const canvasWrapper = document.createElement("div");

View File

@ -164,9 +164,14 @@ class PDFRenderingQueue {
break; break;
case RenderingStates.INITIAL: case RenderingStates.INITIAL:
this.highestPriorityPage = view.renderingId; this.highestPriorityPage = view.renderingId;
view.draw().finally(() => { view
this.renderHighestPriority(); .draw()
}); .finally(() => {
this.renderHighestPriority();
})
.catch(reason => {
console.error(`renderView: "${reason}"`);
});
break; break;
} }
return true; return true;

View File

@ -295,6 +295,13 @@ class PDFThumbnailView {
console.error("Must be in new state before drawing"); console.error("Must be in new state before drawing");
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
const { pdfPage } = this;
if (!pdfPage) {
this.renderingState = RenderingStates.FINISHED;
return Promise.reject(new Error("pdfPage is not loaded"));
}
this.renderingState = RenderingStates.RUNNING; this.renderingState = RenderingStates.RUNNING;
const renderCapability = createPromiseCapability(); const renderCapability = createPromiseCapability();
@ -339,7 +346,7 @@ class PDFThumbnailView {
canvasContext: ctx, canvasContext: ctx,
viewport: drawViewport, viewport: drawViewport,
}; };
const renderTask = (this.renderTask = this.pdfPage.render(renderContext)); const renderTask = (this.renderTask = pdfPage.render(renderContext));
renderTask.onContinue = renderContinueCallback; renderTask.onContinue = renderContinueCallback;
renderTask.promise.then( renderTask.promise.then(