/src/xpdf-4.06/xpdf/XRef.cc
Line | Count | Source |
1 | | //======================================================================== |
2 | | // |
3 | | // XRef.cc |
4 | | // |
5 | | // Copyright 1996-2003 Glyph & Cog, LLC |
6 | | // |
7 | | //======================================================================== |
8 | | |
9 | | #include <aconf.h> |
10 | | |
11 | | #include <stdlib.h> |
12 | | #include <stddef.h> |
13 | | #include <string.h> |
14 | | #include <ctype.h> |
15 | | #include <limits.h> |
16 | | #include "gmem.h" |
17 | | #include "gmempp.h" |
18 | | #include "gfile.h" |
19 | | #include "Object.h" |
20 | | #include "Stream.h" |
21 | | #include "Lexer.h" |
22 | | #include "Parser.h" |
23 | | #include "Dict.h" |
24 | | #include "Error.h" |
25 | | #include "ErrorCodes.h" |
26 | | #include "XRef.h" |
27 | | |
28 | | //------------------------------------------------------------------------ |
29 | | |
30 | 55.4k | #define xrefSearchSize 1024 // read this many bytes at end of file |
31 | | // to look for 'startxref' |
32 | | |
33 | | //------------------------------------------------------------------------ |
34 | | // Permission bits |
35 | | //------------------------------------------------------------------------ |
36 | | |
37 | 17.7k | #define permPrint (1<<2) |
38 | 17.7k | #define permChange (1<<3) |
39 | 17.7k | #define permCopy (1<<4) |
40 | 17.7k | #define permNotes (1<<5) |
41 | 55.3k | #define defPermFlags 0xfffc |
42 | | |
43 | | //------------------------------------------------------------------------ |
44 | | // XRefPosSet |
45 | | //------------------------------------------------------------------------ |
46 | | |
47 | | class XRefPosSet { |
48 | | public: |
49 | | |
50 | | XRefPosSet(); |
51 | | ~XRefPosSet(); |
52 | | void add(GFileOffset pos); |
53 | | GBool check(GFileOffset pos); |
54 | 1.32k | int getLength() { return len; } |
55 | 1.38k | GFileOffset get(int idx) { return tab[idx]; } |
56 | | |
57 | | private: |
58 | | |
59 | | int find(GFileOffset pos); |
60 | | |
61 | | GFileOffset *tab; |
62 | | int size; |
63 | | int len; |
64 | | }; |
65 | | |
66 | 1.32k | XRefPosSet::XRefPosSet() { |
67 | 1.32k | size = 16; |
68 | 1.32k | len = 0; |
69 | 1.32k | tab = (GFileOffset *)gmallocn(size, sizeof(GFileOffset)); |
70 | 1.32k | } |
71 | | |
72 | 1.32k | XRefPosSet::~XRefPosSet() { |
73 | 1.32k | gfree(tab); |
74 | 1.32k | } |
75 | | |
76 | 1.39k | void XRefPosSet::add(GFileOffset pos) { |
77 | 1.39k | int i; |
78 | | |
79 | 1.39k | i = find(pos); |
80 | 1.39k | if (i < len && tab[i] == pos) { |
81 | 0 | return; |
82 | 0 | } |
83 | 1.39k | if (len == size) { |
84 | 0 | if (size > INT_MAX / 2) { |
85 | 0 | gMemError("Integer overflow in XRefPosSet::add()"); |
86 | 0 | } |
87 | 0 | size *= 2; |
88 | 0 | tab = (GFileOffset *)greallocn(tab, size, sizeof(GFileOffset)); |
89 | 0 | } |
90 | 1.39k | if (i < len) { |
91 | 14 | memmove(&tab[i + 1], &tab[i], (len - i) * sizeof(GFileOffset)); |
92 | 14 | } |
93 | 1.39k | tab[i] = pos; |
94 | 1.39k | ++len; |
95 | 1.39k | } |
96 | | |
97 | 1.40k | GBool XRefPosSet::check(GFileOffset pos) { |
98 | 1.40k | int i; |
99 | | |
100 | 1.40k | i = find(pos); |
101 | 1.40k | return i < len && tab[i] == pos; |
102 | 1.40k | } |
103 | | |
104 | 2.79k | int XRefPosSet::find(GFileOffset pos) { |
105 | 2.79k | int a, b, m; |
106 | | |
107 | 2.79k | a = - 1; |
108 | 2.79k | b = len; |
109 | | // invariant: tab[a] < pos < tab[b] |
110 | 2.96k | while (b - a > 1) { |
111 | 179 | m = (a + b) / 2; |
112 | 179 | if (tab[m] < pos) { |
113 | 124 | a = m; |
114 | 124 | } else if (tab[m] > pos) { |
115 | 46 | b = m; |
116 | 46 | } else { |
117 | 9 | return m; |
118 | 9 | } |
119 | 179 | } |
120 | 2.78k | return b; |
121 | 2.79k | } |
122 | | |
123 | | //------------------------------------------------------------------------ |
124 | | // ObjectStream |
125 | | //------------------------------------------------------------------------ |
126 | | |
127 | | class ObjectStream { |
128 | | public: |
129 | | |
130 | | // Create an object stream, using object number <objStrNum>, |
131 | | // generation 0. |
132 | | ObjectStream(XRef *xref, int objStrNumA, int recursion); |
133 | | |
134 | 5.99k | GBool isOk() { return ok; } |
135 | | |
136 | | ~ObjectStream(); |
137 | | |
138 | | // Return the object number of this object stream. |
139 | 29.4k | int getObjStrNum() { return objStrNum; } |
140 | | |
141 | | // Get the <objIdx>th object from this stream, which should be |
142 | | // object number <objNum>, generation 0. |
143 | | Object *getObject(int objIdx, int objNum, Object *obj); |
144 | | |
145 | | private: |
146 | | |
147 | | int objStrNum; // object number of the object stream |
148 | | int nObjects; // number of objects in the stream |
149 | | Object *objs; // the objects (length = nObjects) |
150 | | int *objNums; // the object numbers (length = nObjects) |
151 | | GBool ok; |
152 | | }; |
153 | | |
154 | 5.99k | ObjectStream::ObjectStream(XRef *xref, int objStrNumA, int recursion) { |
155 | 5.99k | Stream *str; |
156 | 5.99k | Lexer *lexer; |
157 | 5.99k | Parser *parser; |
158 | 5.99k | int *offsets; |
159 | 5.99k | Object objStr, obj1, obj2; |
160 | 5.99k | int first, i; |
161 | | |
162 | 5.99k | objStrNum = objStrNumA; |
163 | 5.99k | nObjects = 0; |
164 | 5.99k | objs = NULL; |
165 | 5.99k | objNums = NULL; |
166 | 5.99k | ok = gFalse; |
167 | | |
168 | 5.99k | if (!xref->fetch(objStrNum, 0, &objStr, recursion)->isStream()) { |
169 | 128 | goto err1; |
170 | 128 | } |
171 | | |
172 | 5.86k | if (!objStr.streamGetDict()->lookup("N", &obj1, recursion)->isInt()) { |
173 | 19 | obj1.free(); |
174 | 19 | goto err1; |
175 | 19 | } |
176 | 5.84k | nObjects = obj1.getInt(); |
177 | 5.84k | obj1.free(); |
178 | 5.84k | if (nObjects <= 0) { |
179 | 18 | goto err1; |
180 | 18 | } |
181 | | |
182 | 5.82k | if (!objStr.streamGetDict()->lookup("First", &obj1, recursion)->isInt()) { |
183 | 192 | obj1.free(); |
184 | 192 | goto err1; |
185 | 192 | } |
186 | 5.63k | first = obj1.getInt(); |
187 | 5.63k | obj1.free(); |
188 | 5.63k | if (first < 0) { |
189 | 39 | goto err1; |
190 | 39 | } |
191 | | |
192 | | // this is an arbitrary limit to avoid integer overflow problems |
193 | | // in the 'new Object[nObjects]' call (Acrobat apparently limits |
194 | | // object streams to 100-200 objects) |
195 | 5.59k | if (nObjects > 1000000) { |
196 | 0 | error(errSyntaxError, -1, "Too many objects in an object stream"); |
197 | 0 | goto err1; |
198 | 0 | } |
199 | 5.59k | objs = new Object[nObjects]; |
200 | 5.59k | objNums = (int *)gmallocn(nObjects, sizeof(int)); |
201 | 5.59k | offsets = (int *)gmallocn(nObjects, sizeof(int)); |
202 | | |
203 | | // parse the header: object numbers and offsets |
204 | 5.59k | objStr.streamReset(); |
205 | 5.59k | obj1.initNull(); |
206 | 5.59k | str = new EmbedStream(objStr.getStream(), &obj1, gTrue, first); |
207 | 5.59k | lexer = new Lexer(xref, str); |
208 | 5.59k | parser = new Parser(xref, lexer, gFalse); |
209 | 53.4k | for (i = 0; i < nObjects; ++i) { |
210 | 48.7k | parser->getObj(&obj1, gTrue); |
211 | 48.7k | parser->getObj(&obj2, gTrue); |
212 | 48.7k | if (!obj1.isInt() || !obj2.isInt()) { |
213 | 452 | obj1.free(); |
214 | 452 | obj2.free(); |
215 | 452 | delete parser; |
216 | 452 | gfree(offsets); |
217 | 452 | goto err2; |
218 | 452 | } |
219 | 48.2k | objNums[i] = obj1.getInt(); |
220 | 48.2k | offsets[i] = obj2.getInt(); |
221 | 48.2k | obj1.free(); |
222 | 48.2k | obj2.free(); |
223 | 48.2k | if (objNums[i] < 0 || offsets[i] < 0 || |
224 | 48.0k | (i > 0 && offsets[i] < offsets[i-1])) { |
225 | 428 | delete parser; |
226 | 428 | gfree(offsets); |
227 | 428 | goto err2; |
228 | 428 | } |
229 | 48.2k | } |
230 | 4.71k | lexer->skipToEOF(); |
231 | 4.71k | delete parser; |
232 | | |
233 | | // skip to the first object - this generally shouldn't be needed, |
234 | | // because offsets[0] is normally 0, but just in case... |
235 | 4.71k | if (offsets[0] > 0) { |
236 | 58 | objStr.getStream()->discardChars(offsets[0]); |
237 | 58 | } |
238 | | |
239 | | // parse the objects |
240 | 41.9k | for (i = 0; i < nObjects; ++i) { |
241 | 37.2k | obj1.initNull(); |
242 | 37.2k | if (i == nObjects - 1) { |
243 | 4.71k | str = new EmbedStream(objStr.getStream(), &obj1, gFalse, 0); |
244 | 32.5k | } else { |
245 | 32.5k | str = new EmbedStream(objStr.getStream(), &obj1, gTrue, |
246 | 32.5k | offsets[i+1] - offsets[i]); |
247 | 32.5k | } |
248 | 37.2k | lexer = new Lexer(xref, str); |
249 | 37.2k | parser = new Parser(xref, lexer, gFalse); |
250 | 37.2k | parser->getObj(&objs[i]); |
251 | 37.2k | lexer->skipToEOF(); |
252 | 37.2k | delete parser; |
253 | 37.2k | } |
254 | | |
255 | 4.71k | gfree(offsets); |
256 | 4.71k | ok = gTrue; |
257 | | |
258 | 5.59k | err2: |
259 | 5.59k | objStr.streamClose(); |
260 | 5.99k | err1: |
261 | 5.99k | objStr.free(); |
262 | 5.99k | } |
263 | | |
264 | 5.99k | ObjectStream::~ObjectStream() { |
265 | 5.99k | int i; |
266 | | |
267 | 5.99k | if (objs) { |
268 | 207k | for (i = 0; i < nObjects; ++i) { |
269 | 202k | objs[i].free(); |
270 | 202k | } |
271 | 5.59k | delete[] objs; |
272 | 5.59k | } |
273 | 5.99k | gfree(objNums); |
274 | 5.99k | } |
275 | | |
276 | 21.5k | Object *ObjectStream::getObject(int objIdx, int objNum, Object *obj) { |
277 | 21.5k | if (objIdx < 0 || objIdx >= nObjects || objNum != objNums[objIdx]) { |
278 | 22 | obj->initNull(); |
279 | 21.4k | } else { |
280 | 21.4k | objs[objIdx].copy(obj); |
281 | 21.4k | } |
282 | 21.5k | return obj; |
283 | 21.5k | } |
284 | | |
285 | | //------------------------------------------------------------------------ |
286 | | // XRef |
287 | | //------------------------------------------------------------------------ |
288 | | |
289 | 55.3k | XRef::XRef(BaseStream *strA, GBool repair) { |
290 | 55.3k | GFileOffset pos; |
291 | 55.3k | Object obj; |
292 | 55.3k | XRefPosSet *posSet; |
293 | 55.3k | int i; |
294 | | |
295 | 55.3k | ok = gTrue; |
296 | 55.3k | errCode = errNone; |
297 | 55.3k | repaired = gFalse; |
298 | 55.3k | size = 0; |
299 | 55.3k | last = -1; |
300 | 55.3k | entries = NULL; |
301 | 55.3k | lastStartxrefPos = 0; |
302 | 55.3k | xrefTablePos = NULL; |
303 | 55.3k | xrefTablePosLen = 0; |
304 | 55.3k | streamEnds = NULL; |
305 | 55.3k | streamEndsLen = 0; |
306 | 7.14M | for (i = 0; i < objStrCacheSize; ++i) { |
307 | 7.08M | objStrs[i] = NULL; |
308 | 7.08M | objStrLastUse[i] = 0; |
309 | 7.08M | } |
310 | 55.3k | objStrCacheLength = 0; |
311 | 55.3k | objStrTime = 0; |
312 | | |
313 | 55.3k | encrypted = gFalse; |
314 | 55.3k | permFlags = defPermFlags; |
315 | 55.3k | ownerPasswordOk = gFalse; |
316 | | |
317 | 941k | for (i = 0; i < xrefCacheSize; ++i) { |
318 | 886k | cache[i].num = -1; |
319 | 886k | } |
320 | | |
321 | 55.3k | #if MULTITHREADED |
322 | 55.3k | gInitMutex(&objStrsMutex); |
323 | 55.3k | gInitMutex(&cacheMutex); |
324 | 55.3k | #endif |
325 | | |
326 | 55.3k | str = strA; |
327 | 55.3k | start = str->getStart(); |
328 | | |
329 | | // if the 'repair' flag is set, try to reconstruct the xref table |
330 | 55.3k | if (repair) { |
331 | 27.6k | if (!(ok = constructXRef())) { |
332 | 9.40k | errCode = errDamaged; |
333 | 9.40k | return; |
334 | 9.40k | } |
335 | 18.2k | repaired = gTrue; |
336 | | |
337 | | // if the 'repair' flag is not set, read the xref table |
338 | 27.7k | } else { |
339 | | |
340 | | // read the trailer |
341 | 27.7k | pos = getStartXref(); |
342 | 27.7k | if (pos == 0) { |
343 | 26.3k | errCode = errDamaged; |
344 | 26.3k | ok = gFalse; |
345 | 26.3k | return; |
346 | 26.3k | } |
347 | | |
348 | | // read the xref table |
349 | 1.32k | posSet = new XRefPosSet(); |
350 | 1.39k | while (readXRef(&pos, posSet, gFalse)) ; |
351 | 1.32k | xrefTablePosLen = posSet->getLength(); |
352 | 1.32k | xrefTablePos = (GFileOffset *)gmallocn(xrefTablePosLen, |
353 | 1.32k | sizeof(GFileOffset)); |
354 | 2.71k | for (i = 0; i < xrefTablePosLen; ++i) { |
355 | 1.38k | xrefTablePos[i] = posSet->get(i); |
356 | 1.38k | } |
357 | 1.32k | delete posSet; |
358 | 1.32k | if (!ok) { |
359 | 1.23k | errCode = errDamaged; |
360 | 1.23k | return; |
361 | 1.23k | } |
362 | 1.32k | } |
363 | | |
364 | | // get the root dictionary (catalog) object |
365 | 18.3k | trailerDict.dictLookupNF("Root", &obj); |
366 | 18.3k | if (obj.isRef()) { |
367 | 18.2k | rootNum = obj.getRefNum(); |
368 | 18.2k | rootGen = obj.getRefGen(); |
369 | 18.2k | obj.free(); |
370 | 18.2k | } else { |
371 | 159 | obj.free(); |
372 | 159 | if (!(ok = constructXRef())) { |
373 | 51 | errCode = errDamaged; |
374 | 51 | return; |
375 | 51 | } |
376 | 159 | } |
377 | | |
378 | | // now set the trailer dictionary's xref pointer so we can fetch |
379 | | // indirect objects from it |
380 | 18.3k | trailerDict.getDict()->setXRef(this); |
381 | 18.3k | } |
382 | | |
383 | 55.2k | XRef::~XRef() { |
384 | 55.2k | int i; |
385 | | |
386 | 939k | for (i = 0; i < xrefCacheSize; ++i) { |
387 | 884k | if (cache[i].num >= 0) { |
388 | 165k | cache[i].obj.free(); |
389 | 165k | } |
390 | 884k | } |
391 | 55.2k | gfree(entries); |
392 | 55.2k | trailerDict.free(); |
393 | 55.2k | if (xrefTablePos) { |
394 | 1.32k | gfree(xrefTablePos); |
395 | 1.32k | } |
396 | 55.2k | if (streamEnds) { |
397 | 15.5k | gfree(streamEnds); |
398 | 15.5k | } |
399 | 7.13M | for (i = 0; i < objStrCacheSize; ++i) { |
400 | 7.07M | if (objStrs[i]) { |
401 | 4.71k | delete objStrs[i]; |
402 | 4.71k | } |
403 | 7.07M | } |
404 | 55.2k | #if MULTITHREADED |
405 | 55.2k | gDestroyMutex(&objStrsMutex); |
406 | 55.2k | gDestroyMutex(&cacheMutex); |
407 | 55.2k | #endif |
408 | 55.2k | } |
409 | | |
410 | | // Read the 'startxref' position. |
411 | 27.7k | GFileOffset XRef::getStartXref() { |
412 | 27.7k | char buf[xrefSearchSize+1]; |
413 | 27.7k | char *p; |
414 | 27.7k | int n, i; |
415 | | |
416 | | // read last xrefSearchSize bytes |
417 | 27.7k | str->setPos(xrefSearchSize, -1); |
418 | 27.7k | n = str->getBlock(buf, xrefSearchSize); |
419 | 27.7k | buf[n] = '\0'; |
420 | | |
421 | | // find startxref |
422 | 21.6M | for (i = n - 9; i >= 0; --i) { |
423 | 21.6M | if (!strncmp(&buf[i], "startxref", 9)) { |
424 | 1.69k | break; |
425 | 1.69k | } |
426 | 21.6M | } |
427 | 27.7k | if (i < 0) { |
428 | 26.0k | return 0; |
429 | 26.0k | } |
430 | 4.22k | for (p = &buf[i+9]; isspace(*p & 0xff); ++p) ; |
431 | 1.69k | lastXRefPos = strToFileOffset(p); |
432 | 1.69k | lastStartxrefPos = str->getPos() - n + i; |
433 | | |
434 | 1.69k | return lastXRefPos; |
435 | 27.7k | } |
436 | | |
437 | | // Read one xref table section. Also reads the associated trailer |
438 | | // dictionary, and returns the prev pointer (if any). The [hybrid] |
439 | | // flag is true when following the XRefStm link in a hybrid-reference |
440 | | // file. |
441 | 1.40k | GBool XRef::readXRef(GFileOffset *pos, XRefPosSet *posSet, GBool hybrid) { |
442 | 1.40k | Parser *parser; |
443 | 1.40k | Object obj; |
444 | 1.40k | GBool more; |
445 | 1.40k | char buf[100]; |
446 | 1.40k | int n, i; |
447 | | |
448 | | // check for a loop in the xref tables |
449 | 1.40k | if (posSet->check(*pos)) { |
450 | 9 | error(errSyntaxWarning, -1, "Infinite loop in xref table"); |
451 | 9 | return gFalse; |
452 | 9 | } |
453 | 1.39k | posSet->add(*pos); |
454 | | |
455 | | // the xref data should either be "xref ..." (for an xref table) or |
456 | | // "nn gg obj << ... >> stream ..." (for an xref stream); possibly |
457 | | // preceded by whitespace |
458 | 1.39k | str->setPos(start + *pos); |
459 | 1.39k | n = str->getBlock(buf, 100); |
460 | 3.67k | for (i = 0; i < n && Lexer::isSpace(buf[i]); ++i) ; |
461 | | |
462 | | // parse an old-style xref table |
463 | 1.39k | if (!hybrid && |
464 | 1.38k | i + 4 < n && |
465 | 1.22k | buf[i] == 'x' && buf[i+1] == 'r' && buf[i+2] == 'e' && buf[i+3] == 'f' && |
466 | 190 | Lexer::isSpace(buf[i+4])) { |
467 | 189 | more = readXRefTable(pos, i + 5, posSet); |
468 | | |
469 | | // parse an xref stream |
470 | 1.20k | } else { |
471 | 1.20k | obj.initNull(); |
472 | 1.20k | parser = new Parser(NULL, |
473 | 1.20k | new Lexer(NULL, |
474 | 1.20k | str->makeSubStream(start + *pos, gFalse, 0, &obj)), |
475 | 1.20k | gTrue); |
476 | 1.20k | if (!parser->getObj(&obj, gTrue)->isInt()) { |
477 | 780 | goto err; |
478 | 780 | } |
479 | 422 | obj.free(); |
480 | 422 | if (!parser->getObj(&obj, gTrue)->isInt()) { |
481 | 173 | goto err; |
482 | 173 | } |
483 | 249 | obj.free(); |
484 | 249 | if (!parser->getObj(&obj, gTrue)->isCmd("obj")) { |
485 | 19 | goto err; |
486 | 19 | } |
487 | 230 | obj.free(); |
488 | 230 | if (!parser->getObj(&obj)->isStream()) { |
489 | 31 | goto err; |
490 | 31 | } |
491 | 199 | more = readXRefStream(obj.getStream(), pos); |
492 | 199 | obj.free(); |
493 | 199 | delete parser; |
494 | 199 | } |
495 | | |
496 | 388 | return more; |
497 | | |
498 | 1.00k | err: |
499 | 1.00k | obj.free(); |
500 | 1.00k | delete parser; |
501 | 1.00k | if (hybrid) { |
502 | 0 | error(errSyntaxError, -1, "Invalid XRefStm link in trailer"); |
503 | 1.00k | } else { |
504 | 1.00k | ok = gFalse; |
505 | 1.00k | } |
506 | 1.00k | return gFalse; |
507 | 1.39k | } |
508 | | |
509 | 189 | GBool XRef::readXRefTable(GFileOffset *pos, int offset, XRefPosSet *posSet) { |
510 | 189 | XRefEntry entry; |
511 | 189 | Parser *parser; |
512 | 189 | Object obj, obj2; |
513 | 189 | char buf[6]; |
514 | 189 | GFileOffset off, pos2; |
515 | 189 | GBool more; |
516 | 189 | int first, n, digit, newSize, gen, i, c; |
517 | | |
518 | 189 | str->setPos(start + *pos + offset); |
519 | | |
520 | 421 | while (1) { |
521 | 2.62k | do { |
522 | 2.62k | c = str->getChar(); |
523 | 2.62k | } while (Lexer::isSpace(c)); |
524 | 419 | if (c == 't') { |
525 | 57 | if (str->getBlock(buf, 6) != 6 || memcmp(buf, "railer", 6)) { |
526 | 11 | goto err1; |
527 | 11 | } |
528 | 46 | break; |
529 | 57 | } |
530 | 362 | if (c < '0' || c > '9') { |
531 | 6 | goto err1; |
532 | 6 | } |
533 | 356 | first = 0; |
534 | 921 | do { |
535 | 921 | digit = c - '0'; |
536 | 921 | if (first > (INT_MAX - digit) / 10) { |
537 | 5 | goto err1; |
538 | 5 | } |
539 | 916 | first = (first * 10) + digit; |
540 | 916 | c = str->getChar(); |
541 | 916 | } while (c >= '0' && c <= '9'); |
542 | 351 | if (!Lexer::isSpace(c)) { |
543 | 13 | goto err1; |
544 | 13 | } |
545 | 573 | do { |
546 | 573 | c = str->getChar(); |
547 | 573 | } while (Lexer::isSpace(c)); |
548 | 338 | n = 0; |
549 | 617 | do { |
550 | 617 | digit = c - '0'; |
551 | 617 | if (n > (INT_MAX - digit) / 10) { |
552 | 6 | goto err1; |
553 | 6 | } |
554 | 611 | n = (n * 10) + digit; |
555 | 611 | c = str->getChar(); |
556 | 611 | } while (c >= '0' && c <= '9'); |
557 | 332 | if (!Lexer::isSpace(c)) { |
558 | 22 | goto err1; |
559 | 22 | } |
560 | 310 | if (first > INT_MAX - n) { |
561 | 7 | goto err1; |
562 | 7 | } |
563 | 303 | if (first + n > size) { |
564 | 149 | newSize = size ? size : 512; |
565 | 324 | do { |
566 | 324 | if (newSize > INT_MAX / 2) { |
567 | 2 | goto err1; |
568 | 2 | } |
569 | 322 | newSize <<= 1; |
570 | 322 | } while (first + n > newSize); |
571 | 147 | entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); |
572 | 19.0M | for (i = size; i < newSize; ++i) { |
573 | 19.0M | entries[i].offset = (GFileOffset)-1; |
574 | 19.0M | entries[i].type = xrefEntryFree; |
575 | 19.0M | } |
576 | 147 | size = newSize; |
577 | 147 | } |
578 | 872 | for (i = first; i < first + n; ++i) { |
579 | 1.40k | do { |
580 | 1.40k | c = str->getChar(); |
581 | 1.40k | } while (Lexer::isSpace(c)); |
582 | 640 | off = 0; |
583 | 5.12k | do { |
584 | 5.12k | off = (off * 10) + (c - '0'); |
585 | 5.12k | c = str->getChar(); |
586 | 5.12k | } while (c >= '0' && c <= '9'); |
587 | 640 | if (!Lexer::isSpace(c)) { |
588 | 34 | goto err1; |
589 | 34 | } |
590 | 606 | entry.offset = off; |
591 | 954 | do { |
592 | 954 | c = str->getChar(); |
593 | 954 | } while (Lexer::isSpace(c)); |
594 | 606 | gen = 0; |
595 | 2.64k | do { |
596 | 2.64k | gen = (gen * 10) + (c - '0'); |
597 | 2.64k | c = str->getChar(); |
598 | 2.64k | } while (c >= '0' && c <= '9'); |
599 | 606 | if (!Lexer::isSpace(c)) { |
600 | 19 | goto err1; |
601 | 19 | } |
602 | 587 | entry.gen = gen; |
603 | 708 | do { |
604 | 708 | c = str->getChar(); |
605 | 708 | } while (Lexer::isSpace(c)); |
606 | 587 | if (c == 'n') { |
607 | 527 | entry.type = xrefEntryUncompressed; |
608 | 527 | } else if (c == 'f') { |
609 | 47 | entry.type = xrefEntryFree; |
610 | 47 | } else { |
611 | 13 | goto err1; |
612 | 13 | } |
613 | 574 | c = str->getChar(); |
614 | 574 | if (!Lexer::isSpace(c)) { |
615 | 3 | goto err1; |
616 | 3 | } |
617 | 571 | if (entries[i].offset == (GFileOffset)-1) { |
618 | 419 | entries[i] = entry; |
619 | | // PDF files of patents from the IBM Intellectual Property |
620 | | // Network have a bug: the xref table claims to start at 1 |
621 | | // instead of 0. |
622 | 419 | if (i == 1 && first == 1 && |
623 | 3 | entries[1].offset == 0 && entries[1].gen == 65535 && |
624 | 1 | entries[1].type == xrefEntryFree) { |
625 | 1 | i = first = 0; |
626 | 1 | entries[0] = entries[1]; |
627 | 1 | entries[1].offset = (GFileOffset)-1; |
628 | 1 | } |
629 | 419 | if (i > last) { |
630 | 68 | last = i; |
631 | 68 | } |
632 | 419 | } |
633 | 571 | } |
634 | 301 | } |
635 | | |
636 | | // read the trailer dictionary |
637 | 48 | obj.initNull(); |
638 | 48 | parser = new Parser(NULL, |
639 | 48 | new Lexer(NULL, |
640 | 48 | str->makeSubStream(str->getPos(), gFalse, 0, &obj)), |
641 | 48 | gTrue); |
642 | 48 | parser->getObj(&obj); |
643 | 48 | delete parser; |
644 | 48 | if (!obj.isDict()) { |
645 | 2 | obj.free(); |
646 | 2 | goto err1; |
647 | 2 | } |
648 | | |
649 | | // get the 'Prev' pointer |
650 | | //~ this can be a 64-bit int (?) |
651 | 46 | obj.getDict()->lookupNF("Prev", &obj2); |
652 | 46 | if (obj2.isInt()) { |
653 | 17 | *pos = (GFileOffset)(Guint)obj2.getInt(); |
654 | 17 | more = gTrue; |
655 | 29 | } else if (obj2.isRef()) { |
656 | | // certain buggy PDF generators generate "/Prev NNN 0 R" instead |
657 | | // of "/Prev NNN" |
658 | 0 | *pos = (GFileOffset)(Guint)obj2.getRefNum(); |
659 | 0 | more = gTrue; |
660 | 29 | } else { |
661 | 29 | more = gFalse; |
662 | 29 | } |
663 | 46 | obj2.free(); |
664 | | |
665 | | // save the first trailer dictionary |
666 | 46 | if (trailerDict.isNone()) { |
667 | 33 | obj.copy(&trailerDict); |
668 | 33 | } |
669 | | |
670 | | // check for an 'XRefStm' key |
671 | | //~ this can be a 64-bit int (?) |
672 | 46 | if (obj.getDict()->lookup("XRefStm", &obj2)->isInt()) { |
673 | 3 | pos2 = (GFileOffset)(Guint)obj2.getInt(); |
674 | 3 | readXRef(&pos2, posSet, gTrue); |
675 | 3 | if (!ok) { |
676 | 0 | obj2.free(); |
677 | 0 | obj.free(); |
678 | 0 | goto err1; |
679 | 0 | } |
680 | 3 | } |
681 | 46 | obj2.free(); |
682 | | |
683 | 46 | obj.free(); |
684 | 46 | return more; |
685 | | |
686 | 143 | err1: |
687 | 143 | ok = gFalse; |
688 | 143 | return gFalse; |
689 | 46 | } |
690 | | |
691 | 199 | GBool XRef::readXRefStream(Stream *xrefStr, GFileOffset *pos) { |
692 | 199 | Dict *dict; |
693 | 199 | int w[3]; |
694 | 199 | GBool more; |
695 | 199 | Object obj, obj2, idx; |
696 | 199 | int newSize, first, n, i; |
697 | | |
698 | 199 | dict = xrefStr->getDict(); |
699 | | |
700 | 199 | if (!dict->lookupNF("Size", &obj)->isInt()) { |
701 | 9 | goto err1; |
702 | 9 | } |
703 | 190 | newSize = obj.getInt(); |
704 | 190 | obj.free(); |
705 | 190 | if (newSize < 0) { |
706 | 2 | goto err1; |
707 | 2 | } |
708 | 188 | if (newSize > size) { |
709 | 146 | entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); |
710 | 71.5k | for (i = size; i < newSize; ++i) { |
711 | 71.4k | entries[i].offset = (GFileOffset)-1; |
712 | 71.4k | entries[i].type = xrefEntryFree; |
713 | 71.4k | } |
714 | 146 | size = newSize; |
715 | 146 | } |
716 | | |
717 | 188 | if (!dict->lookupNF("W", &obj)->isArray() || |
718 | 185 | obj.arrayGetLength() < 3) { |
719 | 4 | goto err1; |
720 | 4 | } |
721 | 730 | for (i = 0; i < 3; ++i) { |
722 | 549 | if (!obj.arrayGet(i, &obj2)->isInt()) { |
723 | 3 | obj2.free(); |
724 | 3 | goto err1; |
725 | 3 | } |
726 | 546 | w[i] = obj2.getInt(); |
727 | 546 | obj2.free(); |
728 | 546 | } |
729 | 181 | obj.free(); |
730 | 181 | if (w[0] < 0 || w[0] > 8 || |
731 | 178 | w[1] < 0 || w[1] > 8 || |
732 | 178 | w[2] < 0 || w[2] > 8) { |
733 | 5 | goto err0; |
734 | 5 | } |
735 | | |
736 | 176 | xrefStr->reset(); |
737 | 176 | dict->lookupNF("Index", &idx); |
738 | 176 | if (idx.isArray()) { |
739 | 175 | for (i = 0; i+1 < idx.arrayGetLength(); i += 2) { |
740 | 123 | if (!idx.arrayGet(i, &obj)->isInt()) { |
741 | 6 | idx.free(); |
742 | 6 | goto err1; |
743 | 6 | } |
744 | 117 | first = obj.getInt(); |
745 | 117 | obj.free(); |
746 | 117 | if (!idx.arrayGet(i+1, &obj)->isInt()) { |
747 | 3 | idx.free(); |
748 | 3 | goto err1; |
749 | 3 | } |
750 | 114 | n = obj.getInt(); |
751 | 114 | obj.free(); |
752 | 114 | if (first < 0 || n < 0 || |
753 | 113 | !readXRefStreamSection(xrefStr, w, first, n)) { |
754 | 18 | idx.free(); |
755 | 18 | goto err0; |
756 | 18 | } |
757 | 114 | } |
758 | 97 | } else { |
759 | 97 | if (!readXRefStreamSection(xrefStr, w, 0, newSize)) { |
760 | 37 | idx.free(); |
761 | 37 | goto err0; |
762 | 37 | } |
763 | 97 | } |
764 | 112 | idx.free(); |
765 | | |
766 | | //~ this can be a 64-bit int (?) |
767 | 112 | dict->lookupNF("Prev", &obj); |
768 | 112 | if (obj.isInt()) { |
769 | 59 | *pos = (GFileOffset)(Guint)obj.getInt(); |
770 | 59 | more = gTrue; |
771 | 59 | } else { |
772 | 53 | more = gFalse; |
773 | 53 | } |
774 | 112 | obj.free(); |
775 | 112 | if (trailerDict.isNone()) { |
776 | 78 | trailerDict.initDict(dict); |
777 | 78 | } |
778 | | |
779 | 112 | return more; |
780 | | |
781 | 27 | err1: |
782 | 27 | obj.free(); |
783 | 87 | err0: |
784 | 87 | ok = gFalse; |
785 | 87 | return gFalse; |
786 | 27 | } |
787 | | |
788 | 210 | GBool XRef::readXRefStreamSection(Stream *xrefStr, int *w, int first, int n) { |
789 | 210 | long long type, gen, offset; |
790 | 210 | int c, newSize, i, j; |
791 | | |
792 | 210 | if (first + n < 0) { |
793 | 0 | return gFalse; |
794 | 0 | } |
795 | 210 | if (first + n > size) { |
796 | 27 | for (newSize = size ? 2 * size : 1024; |
797 | 138 | first + n > newSize && newSize > 0; |
798 | 111 | newSize <<= 1) ; |
799 | 27 | if (newSize < 0) { |
800 | 0 | return gFalse; |
801 | 0 | } |
802 | 27 | entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); |
803 | 11.4M | for (i = size; i < newSize; ++i) { |
804 | 11.4M | entries[i].offset = (GFileOffset)-1; |
805 | 11.4M | entries[i].type = xrefEntryFree; |
806 | 11.4M | } |
807 | 27 | size = newSize; |
808 | 27 | } |
809 | 5.32k | for (i = first; i < first + n; ++i) { |
810 | 5.16k | if (w[0] == 0) { |
811 | 1.36k | type = 1; |
812 | 3.80k | } else { |
813 | 7.67k | for (type = 0, j = 0; j < w[0]; ++j) { |
814 | 3.88k | if ((c = xrefStr->getChar()) == EOF) { |
815 | 15 | return gFalse; |
816 | 15 | } |
817 | 3.86k | type = (type << 8) + c; |
818 | 3.86k | } |
819 | 3.80k | } |
820 | 12.9k | for (offset = 0, j = 0; j < w[1]; ++j) { |
821 | 7.81k | if ((c = xrefStr->getChar()) == EOF) { |
822 | 8 | return gFalse; |
823 | 8 | } |
824 | 7.81k | offset = (offset << 8) + c; |
825 | 7.81k | } |
826 | 5.14k | if (offset < 0 || offset > GFILEOFFSET_MAX) { |
827 | 2 | return gFalse; |
828 | 2 | } |
829 | 10.3k | for (gen = 0, j = 0; j < w[2]; ++j) { |
830 | 5.17k | if ((c = xrefStr->getChar()) == EOF) { |
831 | 10 | return gFalse; |
832 | 10 | } |
833 | 5.16k | gen = (gen << 8) + c; |
834 | 5.16k | } |
835 | | // some PDF generators include a free entry with gen=0xffffffff |
836 | 5.12k | if ((gen < 0 || gen > INT_MAX) && type != 0) { |
837 | 3 | return gFalse; |
838 | 3 | } |
839 | 5.12k | if (entries[i].offset == (GFileOffset)-1) { |
840 | 4.91k | switch (type) { |
841 | 232 | case 0: |
842 | 232 | entries[i].offset = (GFileOffset)offset; |
843 | 232 | entries[i].gen = (int)gen; |
844 | 232 | entries[i].type = xrefEntryFree; |
845 | 232 | break; |
846 | 2.13k | case 1: |
847 | 2.13k | entries[i].offset = (GFileOffset)offset; |
848 | 2.13k | entries[i].gen = (int)gen; |
849 | 2.13k | entries[i].type = xrefEntryUncompressed; |
850 | 2.13k | break; |
851 | 2.53k | case 2: |
852 | 2.53k | entries[i].offset = (GFileOffset)offset; |
853 | 2.53k | entries[i].gen = (int)gen; |
854 | 2.53k | entries[i].type = xrefEntryCompressed; |
855 | 2.53k | break; |
856 | 16 | default: |
857 | 16 | return gFalse; |
858 | 4.91k | } |
859 | 4.90k | if (i > last) { |
860 | 3.00k | last = i; |
861 | 3.00k | } |
862 | 4.90k | } |
863 | 5.12k | } |
864 | | |
865 | 156 | return gTrue; |
866 | 210 | } |
867 | | |
868 | | // Attempt to construct an xref table for a damaged file. |
869 | 27.7k | GBool XRef::constructXRef() { |
870 | 27.7k | int *streamObjNums = NULL; |
871 | 27.7k | int streamObjNumsLen = 0; |
872 | 27.7k | int streamObjNumsSize = 0; |
873 | 27.7k | int lastObjNum = -1; |
874 | 27.7k | rootNum = -1; |
875 | 27.7k | int streamEndsSize = 0; |
876 | 27.7k | streamEndsLen = 0; |
877 | 27.7k | char buf[4096 + 1]; |
878 | 27.7k | str->reset(); |
879 | 27.7k | GFileOffset bufPos = start; |
880 | 27.7k | char *p = buf; |
881 | 27.7k | char *end = buf; |
882 | 27.7k | GBool startOfLine = gTrue; |
883 | 27.7k | GBool space = gTrue; |
884 | 27.7k | GBool eof = gFalse; |
885 | 251M | while (1) { |
886 | 251M | if (end - p < 256 && !eof) { |
887 | 87.4k | memcpy(buf, p, end - p); |
888 | 87.4k | bufPos += p - buf; |
889 | 87.4k | p = buf + (end - p); |
890 | 87.4k | int n = (int)(buf + 4096 - p); |
891 | 87.4k | int m = str->getBlock(p, n); |
892 | 87.4k | end = p + m; |
893 | 87.4k | *end = '\0'; |
894 | 87.4k | p = buf; |
895 | 87.4k | eof = m < n; |
896 | 87.4k | } |
897 | 251M | if (p == end && eof) { |
898 | 27.7k | break; |
899 | 27.7k | } |
900 | 251M | if (startOfLine && !strncmp(p, "trailer", 7)) { |
901 | 32.3k | constructTrailerDict((GFileOffset)(bufPos + (p + 7 - buf))); |
902 | 32.3k | p += 7; |
903 | 32.3k | startOfLine = gFalse; |
904 | 32.3k | space = gFalse; |
905 | 251M | } else if (startOfLine && !strncmp(p, "endstream", 9)) { |
906 | 86.8k | if (streamEndsLen == streamEndsSize) { |
907 | 15.7k | streamEndsSize += 64; |
908 | 15.7k | streamEnds = (GFileOffset *)greallocn(streamEnds, streamEndsSize, |
909 | 15.7k | sizeof(GFileOffset)); |
910 | 15.7k | } |
911 | 86.8k | streamEnds[streamEndsLen++] = (GFileOffset)(bufPos + (p - buf)); |
912 | 86.8k | p += 9; |
913 | 86.8k | startOfLine = gFalse; |
914 | 86.8k | space = gFalse; |
915 | 251M | } else if (space && *p >= '0' && *p <= '9') { |
916 | 3.88M | p = constructObjectEntry(p, (GFileOffset)(bufPos + (p - buf)), |
917 | 3.88M | &lastObjNum); |
918 | 3.88M | startOfLine = gFalse; |
919 | 3.88M | space = gFalse; |
920 | 247M | } else if (p[0] == '>' && p[1] == '>') { |
921 | 826k | p += 2; |
922 | 826k | startOfLine = gFalse; |
923 | 826k | space = gFalse; |
924 | | // skip any PDF whitespace except for '\0' |
925 | 1.35M | while (*p == '\t' || *p == '\n' || *p == '\x0c' || |
926 | 1.00M | *p == '\r' || *p == ' ') { |
927 | 530k | if (*p == '\n' || *p == '\r') { |
928 | 409k | startOfLine = gTrue; |
929 | 409k | } |
930 | 530k | space = gTrue; |
931 | 530k | ++p; |
932 | 530k | } |
933 | 826k | if (!strncmp(p, "stream", 6)) { |
934 | 266k | if (lastObjNum >= 0) { |
935 | 264k | if (streamObjNumsLen == streamObjNumsSize) { |
936 | 27.2k | streamObjNumsSize += 64; |
937 | 27.2k | streamObjNums = (int *)greallocn(streamObjNums, streamObjNumsSize, |
938 | 27.2k | sizeof(int)); |
939 | 27.2k | } |
940 | 264k | streamObjNums[streamObjNumsLen++] = lastObjNum; |
941 | 264k | } |
942 | 266k | p += 6; |
943 | 266k | startOfLine = gFalse; |
944 | 266k | space = gFalse; |
945 | 266k | } |
946 | 246M | } else { |
947 | 246M | if (*p == '\n' || *p == '\r') { |
948 | 6.32M | startOfLine = gTrue; |
949 | 6.32M | space = gTrue; |
950 | 240M | } else if (Lexer::isSpace(*p & 0xff)) { |
951 | 19.7M | space = gTrue; |
952 | 220M | } else { |
953 | 220M | startOfLine = gFalse; |
954 | 220M | space = gFalse; |
955 | 220M | } |
956 | 246M | ++p; |
957 | 246M | } |
958 | 251M | } |
959 | | |
960 | | // read each stream object, check for xref or object stream |
961 | 291k | for (int i = 0; i < streamObjNumsLen; ++i) { |
962 | 263k | Object obj; |
963 | 263k | fetch(streamObjNums[i], entries[streamObjNums[i]].gen, &obj); |
964 | 263k | if (obj.isStream()) { |
965 | 229k | Dict *dict = obj.streamGetDict(); |
966 | 229k | Object type; |
967 | 229k | dict->lookup("Type", &type); |
968 | 229k | if (type.isName("XRef")) { |
969 | 3.46k | saveTrailerDict(dict, gTrue); |
970 | 225k | } else if (type.isName("ObjStm")) { |
971 | 134k | constructObjectStreamEntries(&obj, streamObjNums[i]); |
972 | 134k | } |
973 | 229k | type.free(); |
974 | 229k | } |
975 | 263k | obj.free(); |
976 | 263k | } |
977 | | |
978 | 27.7k | gfree(streamObjNums); |
979 | | |
980 | | // if the file is encrypted, then any objects fetched here will be |
981 | | // incorrect (because decryption is not yet enabled), so clear the |
982 | | // cache to avoid that problem |
983 | 469k | for (int i = 0; i < xrefCacheSize; ++i) { |
984 | 442k | if (cache[i].num >= 0) { |
985 | 90.9k | cache[i].obj.free(); |
986 | 90.9k | cache[i].num = -1; |
987 | 90.9k | } |
988 | 442k | } |
989 | | |
990 | 27.7k | if (rootNum < 0) { |
991 | 9.45k | error(errSyntaxError, -1, "Couldn't find trailer dictionary"); |
992 | 9.45k | return gFalse; |
993 | 9.45k | } |
994 | 18.2k | return gTrue; |
995 | 27.7k | } |
996 | | |
997 | | // Attempt to construct a trailer dict at [pos] in the stream. |
998 | 32.3k | void XRef::constructTrailerDict(GFileOffset pos) { |
999 | 32.3k | Object newTrailerDict, obj; |
1000 | 32.3k | obj.initNull(); |
1001 | 32.3k | Parser *parser = |
1002 | 32.3k | new Parser(NULL, |
1003 | 32.3k | new Lexer(NULL, |
1004 | 32.3k | str->makeSubStream(pos, gFalse, 0, &obj)), |
1005 | 32.3k | gFalse); |
1006 | 32.3k | parser->getObj(&newTrailerDict); |
1007 | 32.3k | if (newTrailerDict.isDict()) { |
1008 | 23.6k | saveTrailerDict(newTrailerDict.getDict(), gFalse); |
1009 | 23.6k | } |
1010 | 32.3k | newTrailerDict.free(); |
1011 | 32.3k | delete parser; |
1012 | 32.3k | } |
1013 | | |
1014 | | // If [dict] "looks like" a trailer dict (i.e., has a Root entry), |
1015 | | // save it as the trailer dict. |
1016 | 27.1k | void XRef::saveTrailerDict(Dict *dict, GBool isXRefStream) { |
1017 | 27.1k | Object obj; |
1018 | 27.1k | dict->lookupNF("Root", &obj); |
1019 | 27.1k | if (obj.isRef()) { |
1020 | 23.3k | int newRootNum = obj.getRefNum(); |
1021 | | // the xref stream scanning code runs after all objects are found, |
1022 | | // so we can check for a valid root object number at that point |
1023 | 23.3k | if (!isXRefStream || newRootNum <= last) { |
1024 | 23.2k | rootNum = newRootNum; |
1025 | 23.2k | rootGen = obj.getRefGen(); |
1026 | 23.2k | if (!trailerDict.isNone()) { |
1027 | 5.08k | trailerDict.free(); |
1028 | 5.08k | } |
1029 | 23.2k | trailerDict.initDict(dict); |
1030 | 23.2k | } |
1031 | 23.3k | } |
1032 | 27.1k | obj.free(); |
1033 | 27.1k | } |
1034 | | |
1035 | | // Look for an object header ("nnn ggg obj") at [p]. The first |
1036 | | // character at *[p] is a digit. [pos] is the position of *[p]. |
1037 | 3.88M | char *XRef::constructObjectEntry(char *p, GFileOffset pos, int *objNum) { |
1038 | | // we look for non-end-of-line space characters here, to deal with |
1039 | | // situations like: |
1040 | | // nnn <-- garbage digits on a line |
1041 | | // nnn nnn obj <-- actual object |
1042 | | // and we also ignore '\0' (because it's used to terminate the |
1043 | | // buffer in this damage-scanning code) |
1044 | 3.88M | int num = 0; |
1045 | 11.2M | do { |
1046 | 11.2M | num = (num * 10) + (*p - '0'); |
1047 | 11.2M | ++p; |
1048 | 11.2M | } while (*p >= '0' && *p <= '9' && num < 100000000); |
1049 | 3.88M | if (*p != '\t' && *p != '\x0c' && *p != ' ') { |
1050 | 1.34M | return p; |
1051 | 1.34M | } |
1052 | 2.62M | do { |
1053 | 2.62M | ++p; |
1054 | 2.62M | } while (*p == '\t' || *p == '\x0c' || *p == ' '); |
1055 | 2.53M | if (!(*p >= '0' && *p <= '9')) { |
1056 | 424k | return p; |
1057 | 424k | } |
1058 | 2.11M | int gen = 0; |
1059 | 4.13M | do { |
1060 | 4.13M | gen = (gen * 10) + (*p - '0'); |
1061 | 4.13M | ++p; |
1062 | 4.13M | } while (*p >= '0' && *p <= '9' && gen < 100000000); |
1063 | 2.11M | if (*p != '\t' && *p != '\x0c' && *p != ' ') { |
1064 | 114k | return p; |
1065 | 114k | } |
1066 | 2.02M | do { |
1067 | 2.02M | ++p; |
1068 | 2.02M | } while (*p == '\t' || *p == '\x0c' || *p == ' '); |
1069 | 1.99M | if (strncmp(p, "obj", 3)) { |
1070 | 1.54M | return p; |
1071 | 1.54M | } |
1072 | | |
1073 | 449k | if (constructXRefEntry(num, gen, pos - start, xrefEntryUncompressed)) { |
1074 | 449k | *objNum = num; |
1075 | 449k | } |
1076 | | |
1077 | 449k | return p; |
1078 | 1.99M | } |
1079 | | |
1080 | | // Read the header from an object stream, and add xref entries for all |
1081 | | // of its objects. |
1082 | 134k | void XRef::constructObjectStreamEntries(Object *objStr, int objStrObjNum) { |
1083 | 134k | Object obj1, obj2; |
1084 | | |
1085 | | // get the object count |
1086 | 134k | if (!objStr->streamGetDict()->lookup("N", &obj1)->isInt()) { |
1087 | 780 | obj1.free(); |
1088 | 780 | return; |
1089 | 780 | } |
1090 | 133k | int nObjects = obj1.getInt(); |
1091 | 133k | obj1.free(); |
1092 | 133k | if (nObjects <= 0 || nObjects > 1000000) { |
1093 | 527 | return; |
1094 | 527 | } |
1095 | | |
1096 | | // parse the header: object numbers and offsets |
1097 | 133k | Parser *parser = new Parser(NULL, |
1098 | 133k | new Lexer(NULL, objStr->getStream()->copy()), |
1099 | 133k | gFalse); |
1100 | 78.2M | for (int i = 0; i < nObjects; ++i) { |
1101 | 78.1M | parser->getObj(&obj1, gTrue); |
1102 | 78.1M | parser->getObj(&obj2, gTrue); |
1103 | 78.1M | if (obj1.isInt() && obj2.isInt()) { |
1104 | 74.7k | int num = obj1.getInt(); |
1105 | 74.7k | if (num >= 0 && num < 1000000) { |
1106 | 74.1k | constructXRefEntry(num, i, objStrObjNum, xrefEntryCompressed); |
1107 | 74.1k | } |
1108 | 74.7k | } |
1109 | 78.1M | obj2.free(); |
1110 | 78.1M | obj1.free(); |
1111 | 78.1M | } |
1112 | 133k | delete parser; |
1113 | 133k | } |
1114 | | |
1115 | | GBool XRef::constructXRefEntry(int num, int gen, GFileOffset pos, |
1116 | 523k | XRefEntryType type) { |
1117 | 523k | if (num >= size) { |
1118 | 27.7k | int newSize = (num + 1 + 255) & ~255; |
1119 | 27.7k | if (newSize < 0) { |
1120 | 0 | return gFalse; |
1121 | 0 | } |
1122 | 27.7k | entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); |
1123 | 480M | for (int i = size; i < newSize; ++i) { |
1124 | 480M | entries[i].offset = (GFileOffset)-1; |
1125 | 480M | entries[i].type = xrefEntryFree; |
1126 | 480M | } |
1127 | 27.7k | size = newSize; |
1128 | 27.7k | } |
1129 | | |
1130 | 523k | if (entries[num].type == xrefEntryFree || |
1131 | 502k | gen >= entries[num].gen) { |
1132 | 502k | entries[num].offset = pos; |
1133 | 502k | entries[num].gen = gen; |
1134 | 502k | entries[num].type = type; |
1135 | 502k | if (num > last) { |
1136 | 218k | last = num; |
1137 | 218k | } |
1138 | 502k | } |
1139 | | |
1140 | 523k | return gTrue; |
1141 | 523k | } |
1142 | | |
1143 | | void XRef::setEncryption(int permFlagsA, GBool ownerPasswordOkA, |
1144 | | Guchar *fileKeyA, int keyLengthA, int encVersionA, |
1145 | 1.15k | CryptAlgorithm encAlgorithmA) { |
1146 | 1.15k | int i; |
1147 | | |
1148 | 1.15k | encrypted = gTrue; |
1149 | 1.15k | permFlags = permFlagsA; |
1150 | 1.15k | ownerPasswordOk = ownerPasswordOkA; |
1151 | 1.15k | if (keyLengthA <= 32) { |
1152 | 1.15k | keyLength = keyLengthA; |
1153 | 1.15k | } else { |
1154 | 0 | keyLength = 32; |
1155 | 0 | } |
1156 | 11.7k | for (i = 0; i < keyLength; ++i) { |
1157 | 10.6k | fileKey[i] = fileKeyA[i]; |
1158 | 10.6k | } |
1159 | 1.15k | encVersion = encVersionA; |
1160 | 1.15k | encAlgorithm = encAlgorithmA; |
1161 | 1.15k | } |
1162 | | |
1163 | | GBool XRef::getEncryption(int *permFlagsA, GBool *ownerPasswordOkA, |
1164 | | int *keyLengthA, int *encVersionA, |
1165 | 0 | CryptAlgorithm *encAlgorithmA) { |
1166 | 0 | if (!encrypted) { |
1167 | 0 | return gFalse; |
1168 | 0 | } |
1169 | 0 | *permFlagsA = permFlags; |
1170 | 0 | *ownerPasswordOkA = ownerPasswordOk; |
1171 | 0 | *keyLengthA = keyLength; |
1172 | 0 | *encVersionA = encVersion; |
1173 | 0 | *encAlgorithmA = encAlgorithm; |
1174 | 0 | return gTrue; |
1175 | 0 | } |
1176 | | |
1177 | 17.7k | GBool XRef::okToPrint(GBool ignoreOwnerPW) { |
1178 | 17.7k | return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permPrint); |
1179 | 17.7k | } |
1180 | | |
1181 | 17.7k | GBool XRef::okToChange(GBool ignoreOwnerPW) { |
1182 | 17.7k | return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permChange); |
1183 | 17.7k | } |
1184 | | |
1185 | 17.7k | GBool XRef::okToCopy(GBool ignoreOwnerPW) { |
1186 | 17.7k | return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permCopy); |
1187 | 17.7k | } |
1188 | | |
1189 | 17.7k | GBool XRef::okToAddNotes(GBool ignoreOwnerPW) { |
1190 | 17.7k | return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permNotes); |
1191 | 17.7k | } |
1192 | | |
1193 | 2.14M | Object *XRef::fetch(int num, int gen, Object *obj, int recursion) { |
1194 | 2.14M | XRefEntry *e; |
1195 | 2.14M | Parser *parser; |
1196 | 2.14M | Object obj1, obj2, obj3; |
1197 | 2.14M | XRefCacheEntry tmp; |
1198 | 2.14M | int i, j; |
1199 | | |
1200 | | // check for bogus ref - this can happen in corrupted PDF files |
1201 | 2.14M | if (num < 0 || num >= size) { |
1202 | 2.72k | goto err; |
1203 | 2.72k | } |
1204 | | |
1205 | | // check the cache |
1206 | 2.14M | #if MULTITHREADED |
1207 | 2.14M | gLockMutex(&cacheMutex); |
1208 | 2.14M | #endif |
1209 | 2.14M | if (cache[0].num == num && cache[0].gen == gen) { |
1210 | 711k | cache[0].obj.copy(obj); |
1211 | 711k | #if MULTITHREADED |
1212 | 711k | gUnlockMutex(&cacheMutex); |
1213 | 711k | #endif |
1214 | 711k | return obj; |
1215 | 711k | } |
1216 | 14.3M | for (i = 1; i < xrefCacheSize; ++i) { |
1217 | 13.5M | if (cache[i].num == num && cache[i].gen == gen) { |
1218 | 644k | tmp = cache[i]; |
1219 | 2.40M | for (j = i; j > 0; --j) { |
1220 | 1.75M | cache[j] = cache[j - 1]; |
1221 | 1.75M | } |
1222 | 644k | cache[0] = tmp; |
1223 | 644k | cache[0].obj.copy(obj); |
1224 | 644k | #if MULTITHREADED |
1225 | 644k | gUnlockMutex(&cacheMutex); |
1226 | 644k | #endif |
1227 | 644k | return obj; |
1228 | 644k | } |
1229 | 13.5M | } |
1230 | 786k | #if MULTITHREADED |
1231 | 786k | gUnlockMutex(&cacheMutex); |
1232 | 786k | #endif |
1233 | | |
1234 | 786k | e = &entries[num]; |
1235 | 786k | switch (e->type) { |
1236 | | |
1237 | 305k | case xrefEntryUncompressed: |
1238 | 305k | if (e->gen != gen) { |
1239 | 5.85k | goto err; |
1240 | 5.85k | } |
1241 | 299k | obj1.initNull(); |
1242 | 299k | parser = new Parser(this, |
1243 | 299k | new Lexer(this, |
1244 | 299k | str->makeSubStream(start + e->offset, gFalse, 0, &obj1)), |
1245 | 299k | gTrue); |
1246 | 299k | parser->getObj(&obj1, gTrue); |
1247 | 299k | parser->getObj(&obj2, gTrue); |
1248 | 299k | parser->getObj(&obj3, gTrue); |
1249 | 299k | if (!obj1.isInt() || obj1.getInt() != num || |
1250 | 299k | !obj2.isInt() || obj2.getInt() != gen || |
1251 | 299k | !obj3.isCmd("obj")) { |
1252 | 1.92k | obj1.free(); |
1253 | 1.92k | obj2.free(); |
1254 | 1.92k | obj3.free(); |
1255 | 1.92k | delete parser; |
1256 | 1.92k | goto err; |
1257 | 1.92k | } |
1258 | 297k | parser->getObj(obj, gFalse, encrypted ? fileKey : (Guchar *)NULL, |
1259 | 297k | encAlgorithm, keyLength, num, gen, recursion); |
1260 | 297k | obj1.free(); |
1261 | 297k | obj2.free(); |
1262 | 297k | obj3.free(); |
1263 | 297k | delete parser; |
1264 | 297k | break; |
1265 | | |
1266 | 23.0k | case xrefEntryCompressed: |
1267 | | #if 0 // Adobe apparently ignores the generation number on compressed objects |
1268 | | if (gen != 0) { |
1269 | | goto err; |
1270 | | } |
1271 | | #endif |
1272 | 23.0k | if (e->offset >= (GFileOffset)size || |
1273 | 23.0k | entries[e->offset].type != xrefEntryUncompressed) { |
1274 | 298 | error(errSyntaxError, -1, "Invalid object stream"); |
1275 | 298 | goto err; |
1276 | 298 | } |
1277 | 22.7k | if (!getObjectStreamObject((int)e->offset, e->gen, num, obj, recursion)) { |
1278 | 1.27k | goto err; |
1279 | 1.27k | } |
1280 | 21.5k | break; |
1281 | | |
1282 | 458k | default: |
1283 | 458k | goto err; |
1284 | 786k | } |
1285 | | |
1286 | | // put the new object in the cache, throwing away the oldest object |
1287 | | // currently in the cache |
1288 | 318k | #if MULTITHREADED |
1289 | 318k | gLockMutex(&cacheMutex); |
1290 | 318k | #endif |
1291 | 318k | if (cache[xrefCacheSize - 1].num >= 0) { |
1292 | 62.7k | cache[xrefCacheSize - 1].obj.free(); |
1293 | 62.7k | } |
1294 | 5.10M | for (i = xrefCacheSize - 1; i > 0; --i) { |
1295 | 4.78M | cache[i] = cache[i - 1]; |
1296 | 4.78M | } |
1297 | 318k | cache[0].num = num; |
1298 | 318k | cache[0].gen = gen; |
1299 | 318k | obj->copy(&cache[0].obj); |
1300 | 318k | #if MULTITHREADED |
1301 | 318k | gUnlockMutex(&cacheMutex); |
1302 | 318k | #endif |
1303 | | |
1304 | 318k | return obj; |
1305 | | |
1306 | 470k | err: |
1307 | 470k | return obj->initNull(); |
1308 | 786k | } |
1309 | | |
1310 | | GBool XRef::getObjectStreamObject(int objStrNum, int objIdx, |
1311 | 22.7k | int objNum, Object *obj, int recursion) { |
1312 | 22.7k | if (recursion >= objectRecursionLimit) { |
1313 | 0 | return gFalse; |
1314 | 0 | } |
1315 | | |
1316 | | // check for a cached ObjectStream |
1317 | 22.7k | #if MULTITHREADED |
1318 | 22.7k | gLockMutex(&objStrsMutex); |
1319 | 22.7k | #endif |
1320 | 22.7k | ObjectStream *objStr = getObjectStreamFromCache(objStrNum); |
1321 | 22.7k | GBool found = gFalse; |
1322 | 22.7k | if (objStr) { |
1323 | 16.7k | objStr->getObject(objIdx, objNum, obj); |
1324 | 16.7k | cleanObjectStreamCache(); |
1325 | 16.7k | found = gTrue; |
1326 | 16.7k | } |
1327 | 22.7k | #if MULTITHREADED |
1328 | 22.7k | gUnlockMutex(&objStrsMutex); |
1329 | 22.7k | #endif |
1330 | 22.7k | if (found) { |
1331 | 16.7k | return gTrue; |
1332 | 16.7k | } |
1333 | | |
1334 | | // load a new ObjectStream |
1335 | 5.99k | objStr = new ObjectStream(this, objStrNum, recursion + 1); |
1336 | 5.99k | if (!objStr->isOk()) { |
1337 | 1.27k | delete objStr; |
1338 | 1.27k | return gFalse; |
1339 | 1.27k | } |
1340 | 4.71k | objStr->getObject(objIdx, objNum, obj); |
1341 | 4.71k | #if MULTITHREADED |
1342 | 4.71k | gLockMutex(&objStrsMutex); |
1343 | 4.71k | #endif |
1344 | 4.71k | addObjectStreamToCache(objStr); |
1345 | 4.71k | cleanObjectStreamCache(); |
1346 | 4.71k | #if MULTITHREADED |
1347 | 4.71k | gUnlockMutex(&objStrsMutex); |
1348 | 4.71k | #endif |
1349 | 4.71k | return gTrue; |
1350 | 5.99k | } |
1351 | | |
1352 | | // NB: objStrsMutex must be locked when calling this function. |
1353 | 22.7k | ObjectStream *XRef::getObjectStreamFromCache(int objStrNum) { |
1354 | | // check the MRU entry in the cache |
1355 | 22.7k | if (objStrs[0] && objStrs[0]->getObjStrNum() == objStrNum) { |
1356 | 14.4k | ObjectStream *objStr = objStrs[0]; |
1357 | 14.4k | objStrLastUse[0] = objStrTime++; |
1358 | 14.4k | return objStr; |
1359 | 14.4k | } |
1360 | | |
1361 | | // check the rest of the cache |
1362 | 15.1k | for (int i = 1; i < objStrCacheLength; ++i) { |
1363 | 9.17k | if (objStrs[i] && objStrs[i]->getObjStrNum() == objStrNum) { |
1364 | 2.30k | ObjectStream *objStr = objStrs[i]; |
1365 | 6.46k | for (int j = i; j > 0; --j) { |
1366 | 4.16k | objStrs[j] = objStrs[j - 1]; |
1367 | 4.16k | objStrLastUse[j] = objStrLastUse[j - 1]; |
1368 | 4.16k | } |
1369 | 2.30k | objStrs[0] = objStr; |
1370 | 2.30k | objStrLastUse[0] = objStrTime++; |
1371 | 2.30k | return objStr; |
1372 | 2.30k | } |
1373 | 9.17k | } |
1374 | | |
1375 | 5.99k | return NULL; |
1376 | 8.29k | } |
1377 | | |
1378 | | // NB: objStrsMutex must be locked when calling this function. |
1379 | 4.71k | void XRef::addObjectStreamToCache(ObjectStream *objStr) { |
1380 | | // add to the cache |
1381 | 4.71k | if (objStrCacheLength == objStrCacheSize) { |
1382 | 0 | delete objStrs[objStrCacheSize - 1]; |
1383 | 0 | --objStrCacheLength; |
1384 | 0 | } |
1385 | 10.4k | for (int j = objStrCacheLength; j > 0; --j) { |
1386 | 5.72k | objStrs[j] = objStrs[j - 1]; |
1387 | 5.72k | objStrLastUse[j] = objStrLastUse[j - 1]; |
1388 | 5.72k | } |
1389 | 4.71k | ++objStrCacheLength; |
1390 | 4.71k | objStrs[0] = objStr; |
1391 | 4.71k | objStrLastUse[0] = objStrTime++; |
1392 | 4.71k | } |
1393 | | |
1394 | | // If the oldest (least recently used) entry in the object stream |
1395 | | // cache is more than objStrCacheTimeout accesses old (hasn't been |
1396 | | // used in the last objStrCacheTimeout accesses), eject it from the |
1397 | | // cache. |
1398 | | // NB: objStrsMutex must be locked when calling this function. |
1399 | 21.5k | void XRef::cleanObjectStreamCache() { |
1400 | | // NB: objStrTime and objStrLastUse[] are unsigned ints, so the |
1401 | | // mod-2^32 arithmetic makes the subtraction work out, even if the |
1402 | | // time wraps around. |
1403 | 21.5k | if (objStrCacheLength > 1 && |
1404 | 15.0k | objStrTime - objStrLastUse[objStrCacheLength - 1] |
1405 | 15.0k | > objStrCacheTimeout) { |
1406 | 0 | delete objStrs[objStrCacheLength - 1]; |
1407 | 0 | objStrs[objStrCacheLength - 1] = NULL; |
1408 | 0 | --objStrCacheLength; |
1409 | 0 | } |
1410 | 21.5k | } |
1411 | | |
1412 | 17.6k | Object *XRef::getDocInfo(Object *obj) { |
1413 | 17.6k | return trailerDict.dictLookup("Info", obj); |
1414 | 17.6k | } |
1415 | | |
1416 | | // Added for the pdftex project. |
1417 | 0 | Object *XRef::getDocInfoNF(Object *obj) { |
1418 | 0 | return trailerDict.dictLookupNF("Info", obj); |
1419 | 0 | } |
1420 | | |
1421 | 180k | GBool XRef::getStreamEnd(GFileOffset streamStart, GFileOffset *streamEnd) { |
1422 | 180k | int a, b, m; |
1423 | | |
1424 | 180k | if (streamEndsLen == 0 || |
1425 | 155k | streamStart > streamEnds[streamEndsLen - 1]) { |
1426 | 45.6k | return gFalse; |
1427 | 45.6k | } |
1428 | | |
1429 | 134k | a = -1; |
1430 | 134k | b = streamEndsLen - 1; |
1431 | | // invariant: streamEnds[a] < streamStart <= streamEnds[b] |
1432 | 552k | while (b - a > 1) { |
1433 | 417k | m = (a + b) / 2; |
1434 | 417k | if (streamStart <= streamEnds[m]) { |
1435 | 186k | b = m; |
1436 | 230k | } else { |
1437 | 230k | a = m; |
1438 | 230k | } |
1439 | 417k | } |
1440 | 134k | *streamEnd = streamEnds[b]; |
1441 | 134k | return gTrue; |
1442 | 180k | } |
1443 | | |
1444 | 1.69k | GFileOffset XRef::strToFileOffset(char *s) { |
1445 | 1.69k | GFileOffset x, d; |
1446 | 1.69k | char *p; |
1447 | | |
1448 | 1.69k | x = 0; |
1449 | 5.11k | for (p = s; *p && isdigit(*p & 0xff); ++p) { |
1450 | 3.43k | d = *p - '0'; |
1451 | 3.43k | if (x > (GFILEOFFSET_MAX - d) / 10) { |
1452 | 11 | break; |
1453 | 11 | } |
1454 | 3.42k | x = 10 * x + d; |
1455 | 3.42k | } |
1456 | 1.69k | return x; |
1457 | 1.69k | } |