Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions lib/xlsx/xform/drawing/drawing-xform.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,24 @@ class DrawingXform extends BaseXform {
switch (node.name) {
case this.tag:
this.reset();
this._rootTag = undefined;
this.model = {
anchors: [],
};
break;
default:
this.parser = this.map[node.name];
if (this.parser) {
this.parser.parseOpen(node);
// If we have not yet opened a model, this is the root element of a
// drawing file with a non-standard namespace (e.g. not xdr:wsDr).
// Capture the actual root tag so parseClose can signal loop-exit when
// the corresponding close tag arrives, preventing a StreamBuf hang.
if (!this.model) {
this._rootTag = node.name;
this.model = {anchors: []};
} else {
this.parser = this.map[node.name];
if (this.parser) {
this.parser.parseOpen(node);
}
}
break;
}
Expand All @@ -85,6 +95,13 @@ class DrawingXform extends BaseXform {
case this.tag:
return false;
default:
// Return false (loop-exit signal) when the actual root element closes,
// even if it used a non-standard namespace instead of xdr:wsDr.
// Without this, the StreamBuf for-await loop exhausts all SAX events
// and blocks waiting for stream data that never arrives (issue #56).
if (name === this._rootTag) {
return false;
}
// could be some unrecognised tags
return true;
}
Expand Down
45 changes: 45 additions & 0 deletions spec/integration/issues/issue-56-streambuf-non-xdr-root.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const fs = require('fs');
const JSZip = require('jszip');

const ExcelJS = verquire('exceljs');

// Regression test for protobi/exceljs#56: StreamBuf parser hangs when a
// workbook drawing file uses a root element other than <xdr:wsDr>.
//
// Root cause: DrawingXform.parseClose() only returned the loop-exit signal
// (false) when the close tag matched exactly `xdr:wsDr`. Any other close tag
// fell to `default: return true`, keeping the BaseXform for-await loop alive
// until the underlying stream was exhausted — which never happens with
// StreamBuf unless the caller explicitly ends it.
//
// Fix: capture the actual root tag in parseOpen() and also return false when
// that tag closes.

function buildFixture() {
const srcBuf = fs.readFileSync('./spec/integration/data/test-issue-1575.xlsx');
const unknownTag = '<some:other xmlns:some="http://example.com/unknown-drawing-ns"/>';

return JSZip.loadAsync(srcBuf).then(zip =>
zip.files['xl/drawings/drawing1.xml'].async('string').then(originalXml => {
const openStart = originalXml.indexOf('<xdr:wsDr');
const openEnd = originalXml.indexOf('>', openStart);
const modifiedXml =
originalXml.substring(0, openStart) + unknownTag + originalXml.substring(openEnd + 1);

zip.file('xl/drawings/drawing1.xml', modifiedXml);
return zip.generateAsync({type: 'nodebuffer'});
})
);
}

describe('github issues', () => {
it('issue 56 - StreamBuf hang on drawing with non-xdr:wsDr root element', () => {
const wb = new ExcelJS.Workbook();
return buildFixture()
.then(buf => wb.xlsx.load(buf))
.then(() => {
// Arriving here means the parser exited cleanly — no StreamBuf hang.
expect(true).to.equal(true);
});
}).timeout(5000);
});