Skip to content

Commit fa1133f

Browse files
committed
Merge pull request mozilla#2549 from mduan/issue2391
Skip commands that have too few arguments
2 parents 4fa8268 + 5ab3bb1 commit fa1133f

File tree

5 files changed

+166
-91
lines changed

5 files changed

+166
-91
lines changed

src/evaluator.js

Lines changed: 114 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -30,101 +30,104 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
3030
this.fontIdCounter = 0;
3131
}
3232

33+
// Specifies properties for each command
34+
//
35+
// If variableArgs === true: [0, `numArgs`] expected
36+
// If variableArgs === false: exactly `numArgs` expected
3337
var OP_MAP = {
34-
// Graphics state
35-
w: 'setLineWidth',
36-
J: 'setLineCap',
37-
j: 'setLineJoin',
38-
M: 'setMiterLimit',
39-
d: 'setDash',
40-
ri: 'setRenderingIntent',
41-
i: 'setFlatness',
42-
gs: 'setGState',
43-
q: 'save',
44-
Q: 'restore',
45-
cm: 'transform',
38+
// Graphic state
39+
w: { fnName: 'setLineWidth', numArgs: 1, variableArgs: false },
40+
J: { fnName: 'setLineCap', numArgs: 1, variableArgs: false },
41+
j: { fnName: 'setLineJoin', numArgs: 1, variableArgs: false },
42+
M: { fnName: 'setMiterLimit', numArgs: 1, variableArgs: false },
43+
d: { fnName: 'setDash', numArgs: 2, variableArgs: false },
44+
ri: { fnName: 'setRenderingIntent', numArgs: 1, variableArgs: false },
45+
i: { fnName: 'setFlatness', numArgs: 1, variableArgs: false },
46+
gs: { fnName: 'setGState', numArgs: 1, variableArgs: false },
47+
q: { fnName: 'save', numArgs: 0, variableArgs: false },
48+
Q: { fnName: 'restore', numArgs: 0, variableArgs: false },
49+
cm: { fnName: 'transform', numArgs: 6, variableArgs: false },
4650

4751
// Path
48-
m: 'moveTo',
49-
l: 'lineTo',
50-
c: 'curveTo',
51-
v: 'curveTo2',
52-
y: 'curveTo3',
53-
h: 'closePath',
54-
re: 'rectangle',
55-
S: 'stroke',
56-
s: 'closeStroke',
57-
f: 'fill',
58-
F: 'fill',
59-
'f*': 'eoFill',
60-
B: 'fillStroke',
61-
'B*': 'eoFillStroke',
62-
b: 'closeFillStroke',
63-
'b*': 'closeEOFillStroke',
64-
n: 'endPath',
52+
m: { fnName: 'moveTo', numArgs: 2, variableArgs: false },
53+
l: { fnName: 'lineTo', numArgs: 2, variableArgs: false },
54+
c: { fnName: 'curveTo', numArgs: 6, variableArgs: false },
55+
v: { fnName: 'curveTo2', numArgs: 4, variableArgs: false },
56+
y: { fnName: 'curveTo3', numArgs: 4, variableArgs: false },
57+
h: { fnName: 'closePath', numArgs: 0, variableArgs: false },
58+
re: { fnName: 'rectangle', numArgs: 4, variableArgs: false },
59+
S: { fnName: 'stroke', numArgs: 0, variableArgs: false },
60+
s: { fnName: 'closeStroke', numArgs: 0, variableArgs: false },
61+
f: { fnName: 'fill', numArgs: 0, variableArgs: false },
62+
F: { fnName: 'fill', numArgs: 0, variableArgs: false },
63+
'f*': { fnName: 'eoFill', numArgs: 0, variableArgs: false },
64+
B: { fnName: 'fillStroke', numArgs: 0, variableArgs: false },
65+
'B*': { fnName: 'eoFillStroke', numArgs: 0, variableArgs: false },
66+
b: { fnName: 'closeFillStroke', numArgs: 0, variableArgs: false },
67+
'b*': { fnName: 'closeEOFillStroke', numArgs: 0, variableArgs: false },
68+
n: { fnName: 'endPath', numArgs: 0, variableArgs: false },
6569

6670
// Clipping
67-
W: 'clip',
68-
'W*': 'eoClip',
71+
W: { fnName: 'clip', numArgs: 0, variableArgs: false },
72+
'W*': { fnName: 'eoClip', numArgs: 0, variableArgs: false },
6973

7074
// Text
71-
BT: 'beginText',
72-
ET: 'endText',
73-
Tc: 'setCharSpacing',
74-
Tw: 'setWordSpacing',
75-
Tz: 'setHScale',
76-
TL: 'setLeading',
77-
Tf: 'setFont',
78-
Tr: 'setTextRenderingMode',
79-
Ts: 'setTextRise',
80-
Td: 'moveText',
81-
TD: 'setLeadingMoveText',
82-
Tm: 'setTextMatrix',
83-
'T*': 'nextLine',
84-
Tj: 'showText',
85-
TJ: 'showSpacedText',
86-
"'": 'nextLineShowText',
87-
'"': 'nextLineSetSpacingShowText',
75+
BT: { fnName: 'beginText', numArgs: 0, variableArgs: false },
76+
ET: { fnName: 'endText', numArgs: 0, variableArgs: false },
77+
Tc: { fnName: 'setCharSpacing', numArgs: 1, variableArgs: false },
78+
Tw: { fnName: 'setWordSpacing', numArgs: 1, variableArgs: false },
79+
Tz: { fnName: 'setHScale', numArgs: 1, variableArgs: false },
80+
TL: { fnName: 'setLeading', numArgs: 1, variableArgs: false },
81+
Tf: { fnName: 'setFont', numArgs: 2, variableArgs: false },
82+
Tr: { fnName: 'setTextRenderingMode', numArgs: 1, variableArgs: false },
83+
Ts: { fnName: 'setTextRise', numArgs: 1, variableArgs: false },
84+
Td: { fnName: 'moveText', numArgs: 2, variableArgs: false },
85+
TD: { fnName: 'setLeadingMoveText', numArgs: 2, variableArgs: false },
86+
Tm: { fnName: 'setTextMatrix', numArgs: 6, variableArgs: false },
87+
'T*': { fnName: 'nextLine', numArgs: 0, variableArgs: false },
88+
Tj: { fnName: 'showText', numArgs: 1, variableArgs: false },
89+
TJ: { fnName: 'showSpacedText', numArgs: 1, variableArgs: false },
90+
'\'': { fnName: 'nextLineShowText', numArgs: 1, variableArgs: false },
91+
'"': { fnName: 'nextLineSetSpacingShowText', numArgs: 3,
92+
variableArgs: false },
8893

8994
// Type3 fonts
90-
d0: 'setCharWidth',
91-
d1: 'setCharWidthAndBounds',
95+
d0: { fnName: 'setCharWidth', numArgs: 2, variableArgs: false },
96+
d1: { fnName: 'setCharWidthAndBounds', numArgs: 6, variableArgs: false },
9297

9398
// Color
94-
CS: 'setStrokeColorSpace',
95-
cs: 'setFillColorSpace',
96-
SC: 'setStrokeColor',
97-
SCN: 'setStrokeColorN',
98-
sc: 'setFillColor',
99-
scn: 'setFillColorN',
100-
G: 'setStrokeGray',
101-
g: 'setFillGray',
102-
RG: 'setStrokeRGBColor',
103-
rg: 'setFillRGBColor',
104-
K: 'setStrokeCMYKColor',
105-
k: 'setFillCMYKColor',
99+
CS: { fnName: 'setStrokeColorSpace', numArgs: 1, variableArgs: false },
100+
cs: { fnName: 'setFillColorSpace', numArgs: 1, variableArgs: false },
101+
SC: { fnName: 'setStrokeColor', numArgs: 4, variableArgs: true },
102+
SCN: { fnName: 'setStrokeColorN', numArgs: 33, variableArgs: true },
103+
sc: { fnName: 'setFillColor', numArgs: 4, variableArgs: true },
104+
scn: { fnName: 'setFillColorN', numArgs: 33, variableArgs: true },
105+
G: { fnName: 'setStrokeGray', numArgs: 1, variableArgs: false },
106+
g: { fnName: 'setFillGray', numArgs: 1, variableArgs: false },
107+
RG: { fnName: 'setStrokeRGBColor', numArgs: 3, variableArgs: false },
108+
rg: { fnName: 'setFillRGBColor', numArgs: 3, variableArgs: false },
109+
K: { fnName: 'setStrokeCMYKColor', numArgs: 4, variableArgs: false },
110+
k: { fnName: 'setFillCMYKColor', numArgs: 4, variableArgs: false },
106111

107112
// Shading
108-
sh: 'shadingFill',
113+
sh: { fnName: 'shadingFill', numArgs: 1, variableArgs: false },
109114

110115
// Images
111-
BI: 'beginInlineImage',
112-
ID: 'beginImageData',
113-
EI: 'endInlineImage',
116+
BI: { fnName: 'beginInlineImage', numArgs: 0, variableArgs: false },
117+
ID: { fnName: 'beginImageData', numArgs: 0, variableArgs: false },
118+
EI: { fnName: 'endInlineImage', numArgs: 0, variableArgs: false },
114119

115120
// XObjects
116-
Do: 'paintXObject',
117-
118-
// Marked content
119-
MP: 'markPoint',
120-
DP: 'markPointProps',
121-
BMC: 'beginMarkedContent',
122-
BDC: 'beginMarkedContentProps',
123-
EMC: 'endMarkedContent',
121+
Do: { fnName: 'paintXObject', numArgs: 1, variableArgs: false },
122+
MP: { fnName: 'markPoint', numArgs: 1, variableArgs: false },
123+
DP: { fnName: 'markPointProps', numArgs: 2, variableArgs: false },
124+
BMC: { fnName: 'beginMarkedContent', numArgs: 1, variableArgs: false },
125+
BDC: { fnName: 'beginMarkedContentProps', numArgs: 2, variableArgs: false },
126+
EMC: { fnName: 'endMarkedContent', numArgs: 0, variableArgs: false },
124127

125128
// Compatibility
126-
BX: 'beginCompat',
127-
EX: 'endCompat',
129+
BX: { fnName: 'beginCompat', numArgs: 0, variableArgs: false },
130+
EX: { fnName: 'endCompat', numArgs: 0, variableArgs: false },
128131

129132
// (reserved partial commands for the lexer)
130133
BM: null,
@@ -314,20 +317,51 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
314317
resources = resources || new Dict();
315318
var xobjs = resources.get('XObject') || new Dict();
316319
var patterns = resources.get('Pattern') || new Dict();
320+
// TODO(mduan): pass array of knownCommands rather than OP_MAP
321+
// dictionary
317322
var parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
318323
var res = resources;
319324
var args = [], obj;
320325
var TILING_PATTERN = 1, SHADING_PATTERN = 2;
321326

322327
while (true) {
323328
obj = parser.getObj();
324-
if (isEOF(obj))
329+
if (isEOF(obj)) {
325330
break;
331+
}
326332

327333
if (isCmd(obj)) {
328334
var cmd = obj.cmd;
329-
var fn = OP_MAP[cmd];
330-
assertWellFormed(fn, 'Unknown command "' + cmd + '"');
335+
336+
// Check that the command is valid
337+
var opSpec = OP_MAP[cmd];
338+
if (!opSpec) {
339+
warn('Unknown command "' + cmd + '"');
340+
continue;
341+
}
342+
343+
var fn = opSpec.fnName;
344+
345+
// Validate the number of arguments for the command
346+
if (opSpec.variableArgs) {
347+
if (args.length > opSpec.numArgs) {
348+
info('Command ' + fn + ': expected [0,' + opSpec.numArgs +
349+
'] args, but received ' + args.length + ' args');
350+
}
351+
} else {
352+
if (args.length < opSpec.numArgs) {
353+
// If we receive too few args, it's not possible to possible
354+
// to execute the command, so skip the command
355+
info('Command ' + fn + ': because expected ' + opSpec.numArgs +
356+
' args, but received ' + args.length + ' args; skipping');
357+
args = [];
358+
continue;
359+
} else if (args.length > opSpec.numArgs) {
360+
info('Command ' + fn + ': expected ' + opSpec.numArgs +
361+
' args, but received ' + args.length + ' args');
362+
}
363+
}
364+
331365
// TODO figure out how to type-check vararg functions
332366

333367
if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) {
@@ -509,8 +543,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
509543
argsArray.push(args);
510544
args = [];
511545
} else if (obj != null) {
512-
assertWellFormed(args.length <= 33, 'Too many arguments');
513546
args.push(obj instanceof Dict ? obj.getAll() : obj);
547+
assertWellFormed(args.length <= 33, 'Too many arguments');
514548
}
515549
}
516550

test/pdfs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*.pdf
22

33
!tracemonkey.pdf
4+
!issue2391-1.pdf
45
!ArabicCIDTrueType.pdf
56
!ThuluthFeatures.pdf
67
!arial_unicode_ab_cidfont.pdf

test/pdfs/issue2391-1.pdf

2.72 KB
Binary file not shown.

test/test_manifest.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
"rounds": 1,
1818
"type": "text"
1919
},
20+
{ "id": "issue2391-1",
21+
"file": "pdfs/issue2391-1.pdf",
22+
"md5": "25ae9cb959612e7b343b55da63af2716",
23+
"rounds": 1,
24+
"pageLimit": 1,
25+
"type": "load"
26+
},
2027
{ "id": "html5-canvas-cheat-sheet-load",
2128
"file": "pdfs/canvas.pdf",
2229
"md5": "59510028561daf62e00bf9f6f066b033",

test/unit/evaluator_spec.js

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,12 @@ describe('evaluator', function() {
3232
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
3333
'prefix');
3434
var stream = new StringStream('qTT');
35-
var thrown = false;
36-
try {
37-
evaluator.getOperatorList(stream, new ResourcesMock(), []);
38-
} catch (e) {
39-
thrown = e;
40-
}
41-
expect(thrown).toNotEqual(false);
35+
var result = evaluator.getOperatorList(stream, new ResourcesMock(), []);
36+
37+
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
38+
expect(result.fnArray.length).toEqual(1);
39+
expect(result.fnArray[0]).toEqual('save');
40+
expect(result.argsArray[0].length).toEqual(0);
4241
});
4342

4443
it('should handle one operations', function() {
@@ -84,14 +83,14 @@ describe('evaluator', function() {
8483
'prefix');
8584
var resources = new ResourcesMock();
8685
resources.Res1 = {};
87-
var stream = new StringStream('B*BBMC');
86+
var stream = new StringStream('B*Bf*');
8887
var result = evaluator.getOperatorList(stream, resources, []);
8988

9089
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
9190
expect(result.fnArray.length).toEqual(3);
9291
expect(result.fnArray[0]).toEqual('eoFillStroke');
9392
expect(result.fnArray[1]).toEqual('fillStroke');
94-
expect(result.fnArray[2]).toEqual('beginMarkedContent');
93+
expect(result.fnArray[2]).toEqual('eoFill');
9594
});
9695

9796
it('should handle glued operations and operands', function() {
@@ -112,19 +111,53 @@ describe('evaluator', function() {
112111
it('should handle glued operations and literals', function() {
113112
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
114113
'prefix');
115-
var stream = new StringStream('trueifalserinulli');
114+
var stream = new StringStream('trueifalserinullq');
116115
var result = evaluator.getOperatorList(stream, new ResourcesMock(), []);
117116

118117
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
119118
expect(result.fnArray.length).toEqual(3);
120119
expect(result.fnArray[0]).toEqual('setFlatness');
121120
expect(result.fnArray[1]).toEqual('setRenderingIntent');
122-
expect(result.fnArray[2]).toEqual('setFlatness');
121+
expect(result.fnArray[2]).toEqual('save');
123122
expect(result.argsArray.length).toEqual(3);
124123
expect(result.argsArray[0].length).toEqual(1);
125124
expect(result.argsArray[0][0]).toEqual(true);
126125
expect(result.argsArray[1].length).toEqual(1);
127126
expect(result.argsArray[1][0]).toEqual(false);
127+
expect(result.argsArray[2].length).toEqual(0);
128+
});
129+
});
130+
131+
describe('validateNumberOfArgs', function() {
132+
it('should execute if correct number of arguments', function() {
133+
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
134+
'prefix');
135+
var stream = new StringStream('5 1 d0');
136+
var result = evaluator.getOperatorList(stream, new ResourcesMock(), []);
137+
138+
expect(result.argsArray[0][0]).toEqual(5);
139+
expect(result.argsArray[0][1]).toEqual(1);
140+
expect(result.fnArray[0]).toEqual('setCharWidth');
141+
});
142+
it('should execute if too many arguments', function() {
143+
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
144+
'prefix');
145+
var stream = new StringStream('5 1 4 d0');
146+
var result = evaluator.getOperatorList(stream, new ResourcesMock(), []);
147+
148+
expect(result.argsArray[0][0]).toEqual(5);
149+
expect(result.argsArray[0][1]).toEqual(1);
150+
expect(result.argsArray[0][2]).toEqual(4);
151+
expect(result.fnArray[0]).toEqual('setCharWidth');
152+
});
153+
it('should skip if too few arguments', function() {
154+
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
155+
'prefix');
156+
var stream = new StringStream('5 d0');
157+
var result = evaluator.getOperatorList(stream, new ResourcesMock(), []);
158+
159+
expect(result.argsArray).toEqual([]);
160+
expect(result.fnArray).toEqual([]);
128161
});
129162
});
130163
});

0 commit comments

Comments
 (0)