upgraded to xpdf-3.01pl1
[swftools.git] / pdf2swf / xpdf / XRef.cc
1 //========================================================================
2 //
3 // XRef.cc
4 //
5 // Copyright 1996-2003 Glyph & Cog, LLC
6 //
7 //========================================================================
8
9 #include <aconf.h>
10
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
13 #endif
14
15 #include <stdlib.h>
16 #include <stddef.h>
17 #include <string.h>
18 #include <ctype.h>
19 #include "gmem.h"
20 #include "Object.h"
21 #include "Stream.h"
22 #include "Lexer.h"
23 #include "Parser.h"
24 #include "Dict.h"
25 #include "Error.h"
26 #include "ErrorCodes.h"
27 #include "XRef.h"
28
29 //------------------------------------------------------------------------
30
31 #define xrefSearchSize 1024     // read this many bytes at end of file
32                                 //   to look for 'startxref'
33
34 //------------------------------------------------------------------------
35 // Permission bits
36 //------------------------------------------------------------------------
37
38 #define permPrint    (1<<2)
39 #define permChange   (1<<3)
40 #define permCopy     (1<<4)
41 #define permNotes    (1<<5)
42 #define defPermFlags 0xfffc
43
44 //------------------------------------------------------------------------
45 // ObjectStream
46 //------------------------------------------------------------------------
47
48 class ObjectStream {
49 public:
50
51   // Create an object stream, using object number <objStrNum>,
52   // generation 0.
53   ObjectStream(XRef *xref, int objStrNumA);
54
55   ~ObjectStream();
56
57   // Return the object number of this object stream.
58   int getObjStrNum() { return objStrNum; }
59
60   // Get the <objIdx>th object from this stream, which should be
61   // object number <objNum>, generation 0.
62   Object *getObject(int objIdx, int objNum, Object *obj);
63
64 private:
65
66   int objStrNum;                // object number of the object stream
67   int nObjects;                 // number of objects in the stream
68   Object *objs;                 // the objects (length = nObjects)
69   int *objNums;                 // the object numbers (length = nObjects)
70 };
71
72 ObjectStream::ObjectStream(XRef *xref, int objStrNumA) {
73   Stream *str;
74   Parser *parser;
75   int *offsets;
76   Object objStr, obj1, obj2;
77   int first, i;
78
79   objStrNum = objStrNumA;
80   nObjects = 0;
81   objs = NULL;
82   objNums = NULL;
83
84   if (!xref->fetch(objStrNum, 0, &objStr)->isStream()) {
85     goto err1;
86   }
87
88   if (!objStr.streamGetDict()->lookup("N", &obj1)->isInt()) {
89     obj1.free();
90     goto err1;
91   }
92   nObjects = obj1.getInt();
93   obj1.free();
94   if (nObjects <= 0) {
95     goto err1;
96   }
97
98   if (!objStr.streamGetDict()->lookup("First", &obj1)->isInt()) {
99     obj1.free();
100     goto err1;
101   }
102   first = obj1.getInt();
103   obj1.free();
104   if (first < 0) {
105     goto err1;
106   }
107
108   objs = new Object[nObjects];
109   objNums = (int *)gmallocn(nObjects, sizeof(int));
110   offsets = (int *)gmallocn(nObjects, sizeof(int));
111
112   // parse the header: object numbers and offsets
113   objStr.streamReset();
114   obj1.initNull();
115   str = new EmbedStream(objStr.getStream(), &obj1, gTrue, first);
116   parser = new Parser(xref, new Lexer(xref, str));
117   for (i = 0; i < nObjects; ++i) {
118     parser->getObj(&obj1);
119     parser->getObj(&obj2);
120     if (!obj1.isInt() || !obj2.isInt()) {
121       obj1.free();
122       obj2.free();
123       delete parser;
124       gfree(offsets);
125       goto err1;
126     }
127     objNums[i] = obj1.getInt();
128     offsets[i] = obj2.getInt();
129     obj1.free();
130     obj2.free();
131     if (objNums[i] < 0 || offsets[i] < 0 ||
132         (i > 0 && offsets[i] < offsets[i-1])) {
133       delete parser;
134       gfree(offsets);
135       goto err1;
136     }
137   }
138   while (str->getChar() != EOF) ;
139   delete parser;
140
141   // skip to the first object - this shouldn't be necessary because
142   // the First key is supposed to be equal to offsets[0], but just in
143   // case...
144   for (i = first; i < offsets[0]; ++i) {
145     objStr.getStream()->getChar();
146   }
147
148   // parse the objects
149   for (i = 0; i < nObjects; ++i) {
150     obj1.initNull();
151     if (i == nObjects - 1) {
152       str = new EmbedStream(objStr.getStream(), &obj1, gFalse, 0);
153     } else {
154       str = new EmbedStream(objStr.getStream(), &obj1, gTrue,
155                             offsets[i+1] - offsets[i]);
156     }
157     parser = new Parser(xref, new Lexer(xref, str));
158     parser->getObj(&objs[i]);
159     while (str->getChar() != EOF) ;
160     delete parser;
161   }
162
163   gfree(offsets);
164
165  err1:
166   objStr.free();
167   return;
168 }
169
170 ObjectStream::~ObjectStream() {
171   int i;
172
173   if (objs) {
174     for (i = 0; i < nObjects; ++i) {
175       objs[i].free();
176     }
177     delete[] objs;
178   }
179   gfree(objNums);
180 }
181
182 Object *ObjectStream::getObject(int objIdx, int objNum, Object *obj) {
183   if (objIdx < 0 || objIdx >= nObjects || objNum != objNums[objIdx]) {
184     return obj->initNull();
185   }
186   return objs[objIdx].copy(obj);
187 }
188
189 //------------------------------------------------------------------------
190 // XRef
191 //------------------------------------------------------------------------
192
193 XRef::XRef(BaseStream *strA) {
194   Guint pos;
195   Object obj;
196
197   ok = gTrue;
198   errCode = errNone;
199   size = 0;
200   entries = NULL;
201   streamEnds = NULL;
202   streamEndsLen = 0;
203   objStr = NULL;
204
205   encrypted = gFalse;
206   permFlags = defPermFlags;
207   ownerPasswordOk = gFalse;
208
209   // read the trailer
210   str = strA;
211   start = str->getStart();
212   pos = getStartXref();
213
214   // if there was a problem with the 'startxref' position, try to
215   // reconstruct the xref table
216   if (pos == 0) {
217     if (!(ok = constructXRef())) {
218       errCode = errDamaged;
219       return;
220     }
221
222   // read the xref table
223   } else {
224     while (readXRef(&pos)) ;
225
226     // if there was a problem with the xref table,
227     // try to reconstruct it
228     if (!ok) {
229       if (!(ok = constructXRef())) {
230         errCode = errDamaged;
231         return;
232       }
233     }
234   }
235
236   // get the root dictionary (catalog) object
237   trailerDict.dictLookupNF("Root", &obj);
238   if (obj.isRef()) {
239     rootNum = obj.getRefNum();
240     rootGen = obj.getRefGen();
241     obj.free();
242   } else {
243     obj.free();
244     if (!(ok = constructXRef())) {
245       errCode = errDamaged;
246       return;
247     }
248   }
249
250   // now set the trailer dictionary's xref pointer so we can fetch
251   // indirect objects from it
252   trailerDict.getDict()->setXRef(this);
253 }
254
255 XRef::~XRef() {
256   gfree(entries);
257   trailerDict.free();
258   if (streamEnds) {
259     gfree(streamEnds);
260   }
261   if (objStr) {
262     delete objStr;
263   }
264 }
265
266 // Read the 'startxref' position.
267 Guint XRef::getStartXref() {
268   char buf[xrefSearchSize+1];
269   char *p;
270   int c, n, i;
271
272   // read last xrefSearchSize bytes
273   str->setPos(xrefSearchSize, -1);
274   for (n = 0; n < xrefSearchSize; ++n) {
275     if ((c = str->getChar()) == EOF) {
276       break;
277     }
278     buf[n] = c;
279   }
280   buf[n] = '\0';
281
282   // find startxref
283   for (i = n - 9; i >= 0; --i) {
284     if (!strncmp(&buf[i], "startxref", 9)) {
285       break;
286     }
287   }
288   if (i < 0) {
289     return 0;
290   }
291   for (p = &buf[i+9]; isspace(*p); ++p) ;
292   lastXRefPos = strToUnsigned(p);
293
294   return lastXRefPos;
295 }
296
297 // Read one xref table section.  Also reads the associated trailer
298 // dictionary, and returns the prev pointer (if any).
299 GBool XRef::readXRef(Guint *pos) {
300   Parser *parser;
301   Object obj;
302   GBool more;
303
304   // start up a parser, parse one token
305   obj.initNull();
306   parser = new Parser(NULL,
307              new Lexer(NULL,
308                str->makeSubStream(start + *pos, gFalse, 0, &obj)));
309   parser->getObj(&obj);
310
311   // parse an old-style xref table
312   if (obj.isCmd("xref")) {
313     obj.free();
314     more = readXRefTable(parser, pos);
315
316   // parse an xref stream
317   } else if (obj.isInt()) {
318     obj.free();
319     if (!parser->getObj(&obj)->isInt()) {
320       goto err1;
321     }
322     obj.free();
323     if (!parser->getObj(&obj)->isCmd("obj")) {
324       goto err1;
325     }
326     obj.free();
327     if (!parser->getObj(&obj)->isStream()) {
328       goto err1;
329     }
330     more = readXRefStream(obj.getStream(), pos);
331     obj.free();
332
333   } else {
334     goto err1;
335   }
336
337   delete parser;
338   return more;
339
340  err1:
341   obj.free();
342   delete parser;
343   ok = gFalse;
344   return gFalse;
345 }
346
347 GBool XRef::readXRefTable(Parser *parser, Guint *pos) {
348   XRefEntry entry;
349   GBool more;
350   Object obj, obj2;
351   Guint pos2;
352   int first, n, newSize, i;
353
354   while (1) {
355     parser->getObj(&obj);
356     if (obj.isCmd("trailer")) {
357       obj.free();
358       break;
359     }
360     if (!obj.isInt()) {
361       goto err1;
362     }
363     first = obj.getInt();
364     obj.free();
365     if (!parser->getObj(&obj)->isInt()) {
366       goto err1;
367     }
368     n = obj.getInt();
369     obj.free();
370     if (first < 0 || n < 0 || first + n < 0) {
371       goto err1;
372     }
373     if (first + n > size) {
374       for (newSize = size ? 2 * size : 1024;
375            first + n > newSize && newSize > 0;
376            newSize <<= 1) ;
377       if (newSize < 0) {
378         goto err1;
379       }
380       entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry));
381       for (i = size; i < newSize; ++i) {
382         entries[i].offset = 0xffffffff;
383         entries[i].type = xrefEntryFree;
384       }
385       size = newSize;
386     }
387     for (i = first; i < first + n; ++i) {
388       if (!parser->getObj(&obj)->isInt()) {
389         goto err1;
390       }
391       entry.offset = (Guint)obj.getInt();
392       obj.free();
393       if (!parser->getObj(&obj)->isInt()) {
394         goto err1;
395       }
396       entry.gen = obj.getInt();
397       obj.free();
398       parser->getObj(&obj);
399       if (obj.isCmd("n")) {
400         entry.type = xrefEntryUncompressed;
401       } else if (obj.isCmd("f")) {
402         entry.type = xrefEntryFree;
403       } else {
404         goto err1;
405       }
406       obj.free();
407       if (entries[i].offset == 0xffffffff) {
408         entries[i] = entry;
409         // PDF files of patents from the IBM Intellectual Property
410         // Network have a bug: the xref table claims to start at 1
411         // instead of 0.
412         if (i == 1 && first == 1 &&
413             entries[1].offset == 0 && entries[1].gen == 65535 &&
414             entries[1].type == xrefEntryFree) {
415           i = first = 0;
416           entries[0] = entries[1];
417           entries[1].offset = 0xffffffff;
418         }
419       }
420     }
421   }
422
423   // read the trailer dictionary
424   if (!parser->getObj(&obj)->isDict()) {
425     goto err1;
426   }
427
428   // get the 'Prev' pointer
429   obj.getDict()->lookupNF("Prev", &obj2);
430   if (obj2.isInt()) {
431     *pos = (Guint)obj2.getInt();
432     more = gTrue;
433   } else if (obj2.isRef()) {
434     // certain buggy PDF generators generate "/Prev NNN 0 R" instead
435     // of "/Prev NNN"
436     *pos = (Guint)obj2.getRefNum();
437     more = gTrue;
438   } else {
439     more = gFalse;
440   }
441   obj2.free();
442
443   // save the first trailer dictionary
444   if (trailerDict.isNone()) {
445     obj.copy(&trailerDict);
446   }
447
448   // check for an 'XRefStm' key
449   if (obj.getDict()->lookup("XRefStm", &obj2)->isInt()) {
450     pos2 = (Guint)obj2.getInt();
451     readXRef(&pos2);
452     if (!ok) {
453       obj2.free();
454       goto err1;
455     }
456   }
457   obj2.free();
458
459   obj.free();
460   return more;
461
462  err1:
463   obj.free();
464   ok = gFalse;
465   return gFalse;
466 }
467
468 GBool XRef::readXRefStream(Stream *xrefStr, Guint *pos) {
469   Dict *dict;
470   int w[3];
471   GBool more;
472   Object obj, obj2, idx;
473   int newSize, first, n, i;
474
475   dict = xrefStr->getDict();
476
477   if (!dict->lookupNF("Size", &obj)->isInt()) {
478     goto err1;
479   }
480   newSize = obj.getInt();
481   obj.free();
482   if (newSize < 0) {
483     goto err1;
484   }
485   if (newSize > size) {
486     entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry));
487     for (i = size; i < newSize; ++i) {
488       entries[i].offset = 0xffffffff;
489       entries[i].type = xrefEntryFree;
490     }
491     size = newSize;
492   }
493
494   if (!dict->lookupNF("W", &obj)->isArray() ||
495       obj.arrayGetLength() < 3) {
496     goto err1;
497   }
498   for (i = 0; i < 3; ++i) {
499     if (!obj.arrayGet(i, &obj2)->isInt()) {
500       obj2.free();
501       goto err1;
502     }
503     w[i] = obj2.getInt();
504     obj2.free();
505     if (w[i] < 0 || w[i] > 4) {
506       goto err1;
507     }
508   }
509   obj.free();
510
511   xrefStr->reset();
512   dict->lookupNF("Index", &idx);
513   if (idx.isArray()) {
514     for (i = 0; i+1 < idx.arrayGetLength(); i += 2) {
515       if (!idx.arrayGet(i, &obj)->isInt()) {
516         idx.free();
517         goto err1;
518       }
519       first = obj.getInt();
520       obj.free();
521       if (!idx.arrayGet(i+1, &obj)->isInt()) {
522         idx.free();
523         goto err1;
524       }
525       n = obj.getInt();
526       obj.free();
527       if (first < 0 || n < 0 ||
528           !readXRefStreamSection(xrefStr, w, first, n)) {
529         idx.free();
530         goto err0;
531       }
532     }
533   } else {
534     if (!readXRefStreamSection(xrefStr, w, 0, newSize)) {
535       idx.free();
536       goto err0;
537     }
538   }
539   idx.free();
540
541   dict->lookupNF("Prev", &obj);
542   if (obj.isInt()) {
543     *pos = (Guint)obj.getInt();
544     more = gTrue;
545   } else {
546     more = gFalse;
547   }
548   obj.free();
549   if (trailerDict.isNone()) {
550     trailerDict.initDict(dict);
551   }
552
553   return more;
554
555  err1:
556   obj.free();
557  err0:
558   ok = gFalse;
559   return gFalse;
560 }
561
562 GBool XRef::readXRefStreamSection(Stream *xrefStr, int *w, int first, int n) {
563   Guint offset;
564   int type, gen, c, newSize, i, j;
565
566   if (first + n < 0) {
567     return gFalse;
568   }
569   if (first + n > size) {
570     for (newSize = size ? 2 * size : 1024;
571          first + n > newSize && newSize > 0;
572          newSize <<= 1) ;
573     if (newSize < 0) {
574       return gFalse;
575     }
576     entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry));
577     for (i = size; i < newSize; ++i) {
578       entries[i].offset = 0xffffffff;
579       entries[i].type = xrefEntryFree;
580     }
581     size = newSize;
582   }
583   for (i = first; i < first + n; ++i) {
584     if (w[0] == 0) {
585       type = 1;
586     } else {
587       for (type = 0, j = 0; j < w[0]; ++j) {
588         if ((c = xrefStr->getChar()) == EOF) {
589           return gFalse;
590         }
591         type = (type << 8) + c;
592       }
593     }
594     for (offset = 0, j = 0; j < w[1]; ++j) {
595       if ((c = xrefStr->getChar()) == EOF) {
596         return gFalse;
597       }
598       offset = (offset << 8) + c;
599     }
600     for (gen = 0, j = 0; j < w[2]; ++j) {
601       if ((c = xrefStr->getChar()) == EOF) {
602         return gFalse;
603       }
604       gen = (gen << 8) + c;
605     }
606     if (entries[i].offset == 0xffffffff) {
607       switch (type) {
608       case 0:
609         entries[i].offset = offset;
610         entries[i].gen = gen;
611         entries[i].type = xrefEntryFree;
612         break;
613       case 1:
614         entries[i].offset = offset;
615         entries[i].gen = gen;
616         entries[i].type = xrefEntryUncompressed;
617         break;
618       case 2:
619         entries[i].offset = offset;
620         entries[i].gen = gen;
621         entries[i].type = xrefEntryCompressed;
622         break;
623       default:
624         return gFalse;
625       }
626     }
627   }
628
629   return gTrue;
630 }
631
632 // Attempt to construct an xref table for a damaged file.
633 GBool XRef::constructXRef() {
634   Parser *parser;
635   Object newTrailerDict, obj;
636   char buf[256];
637   Guint pos;
638   int num, gen;
639   int newSize;
640   int streamEndsSize;
641   char *p;
642   int i;
643   GBool gotRoot;
644
645   gfree(entries);
646   size = 0;
647   entries = NULL;
648
649   error(0, "PDF file is damaged - attempting to reconstruct xref table...");
650   gotRoot = gFalse;
651   streamEndsLen = streamEndsSize = 0;
652
653   str->reset();
654   while (1) {
655     pos = str->getPos();
656     if (!str->getLine(buf, 256)) {
657       break;
658     }
659     p = buf;
660
661     // got trailer dictionary
662     if (!strncmp(p, "trailer", 7)) {
663       obj.initNull();
664       parser = new Parser(NULL,
665                  new Lexer(NULL,
666                    str->makeSubStream(pos + 7, gFalse, 0, &obj)));
667       parser->getObj(&newTrailerDict);
668       if (newTrailerDict.isDict()) {
669         newTrailerDict.dictLookupNF("Root", &obj);
670         if (obj.isRef()) {
671           rootNum = obj.getRefNum();
672           rootGen = obj.getRefGen();
673           if (!trailerDict.isNone()) {
674             trailerDict.free();
675           }
676           newTrailerDict.copy(&trailerDict);
677           gotRoot = gTrue;
678         }
679         obj.free();
680       }
681       newTrailerDict.free();
682       delete parser;
683
684     // look for object
685     } else if (isdigit(*p)) {
686       num = atoi(p);
687       if (num > 0) {
688         do {
689           ++p;
690         } while (*p && isdigit(*p));
691         if (isspace(*p)) {
692           do {
693             ++p;
694           } while (*p && isspace(*p));
695           if (isdigit(*p)) {
696             gen = atoi(p);
697             do {
698               ++p;
699             } while (*p && isdigit(*p));
700             if (isspace(*p)) {
701               do {
702                 ++p;
703               } while (*p && isspace(*p));
704               if (!strncmp(p, "obj", 3)) {
705                 if (num >= size) {
706                   newSize = (num + 1 + 255) & ~255;
707                   if (newSize < 0) {
708                     error(-1, "Bad object number");
709                     return gFalse;
710                   }
711                   entries = (XRefEntry *)
712                       greallocn(entries, newSize, sizeof(XRefEntry));
713                   for (i = size; i < newSize; ++i) {
714                     entries[i].offset = 0xffffffff;
715                     entries[i].type = xrefEntryFree;
716                   }
717                   size = newSize;
718                 }
719                 if (entries[num].type == xrefEntryFree ||
720                     gen >= entries[num].gen) {
721                   entries[num].offset = pos - start;
722                   entries[num].gen = gen;
723                   entries[num].type = xrefEntryUncompressed;
724                 }
725               }
726             }
727           }
728         }
729       }
730
731     } else if (!strncmp(p, "endstream", 9)) {
732       if (streamEndsLen == streamEndsSize) {
733         streamEndsSize += 64;
734         streamEnds = (Guint *)greallocn(streamEnds,
735                                         streamEndsSize, sizeof(int));
736       }
737       streamEnds[streamEndsLen++] = pos;
738     }
739   }
740
741   if (gotRoot)
742     return gTrue;
743
744   error(-1, "Couldn't find trailer dictionary");
745   return gFalse;
746 }
747
748 void XRef::setEncryption(int permFlagsA, GBool ownerPasswordOkA,
749                          Guchar *fileKeyA, int keyLengthA, int encVersionA) {
750   int i;
751
752   encrypted = gTrue;
753   permFlags = permFlagsA;
754   ownerPasswordOk = ownerPasswordOkA;
755   if (keyLengthA <= 16) {
756     keyLength = keyLengthA;
757   } else {
758     keyLength = 16;
759   }
760   for (i = 0; i < keyLength; ++i) {
761     fileKey[i] = fileKeyA[i];
762   }
763   encVersion = encVersionA;
764 }
765
766 GBool XRef::okToPrint(GBool ignoreOwnerPW) {
767   return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permPrint);
768 }
769
770 GBool XRef::okToChange(GBool ignoreOwnerPW) {
771   return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permChange);
772 }
773
774 GBool XRef::okToCopy(GBool ignoreOwnerPW) {
775   return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permCopy);
776 }
777
778 GBool XRef::okToAddNotes(GBool ignoreOwnerPW) {
779   return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permNotes);
780 }
781
782 Object *XRef::fetch(int num, int gen, Object *obj) {
783   XRefEntry *e;
784   Parser *parser;
785   Object obj1, obj2, obj3;
786
787   // check for bogus ref - this can happen in corrupted PDF files
788   if (num < 0 || num >= size) {
789     goto err;
790   }
791
792   e = &entries[num];
793   switch (e->type) {
794
795   case xrefEntryUncompressed:
796     if (e->gen != gen) {
797       goto err;
798     }
799     obj1.initNull();
800     parser = new Parser(this,
801                new Lexer(this,
802                  str->makeSubStream(start + e->offset, gFalse, 0, &obj1)));
803     parser->getObj(&obj1);
804     parser->getObj(&obj2);
805     parser->getObj(&obj3);
806     if (!obj1.isInt() || obj1.getInt() != num ||
807         !obj2.isInt() || obj2.getInt() != gen ||
808         !obj3.isCmd("obj")) {
809       obj1.free();
810       obj2.free();
811       obj3.free();
812       delete parser;
813       goto err;
814     }
815     parser->getObj(obj, encrypted ? fileKey : (Guchar *)NULL, keyLength,
816                    num, gen);
817     obj1.free();
818     obj2.free();
819     obj3.free();
820     delete parser;
821     break;
822
823   case xrefEntryCompressed:
824     if (gen != 0) {
825       goto err;
826     }
827     if (!objStr || objStr->getObjStrNum() != (int)e->offset) {
828       if (objStr) {
829         delete objStr;
830       }
831       objStr = new ObjectStream(this, e->offset);
832     }
833     objStr->getObject(e->gen, num, obj);
834     break;
835
836   default:
837     goto err;
838   }
839
840   return obj;
841
842  err:
843   return obj->initNull();
844 }
845
846 Object *XRef::getDocInfo(Object *obj) {
847   return trailerDict.dictLookup("Info", obj);
848 }
849
850 // Added for the pdftex project.
851 Object *XRef::getDocInfoNF(Object *obj) {
852   return trailerDict.dictLookupNF("Info", obj);
853 }
854
855 GBool XRef::getStreamEnd(Guint streamStart, Guint *streamEnd) {
856   int a, b, m;
857
858   if (streamEndsLen == 0 ||
859       streamStart > streamEnds[streamEndsLen - 1]) {
860     return gFalse;
861   }
862
863   a = -1;
864   b = streamEndsLen - 1;
865   // invariant: streamEnds[a] < streamStart <= streamEnds[b]
866   while (b - a > 1) {
867     m = (a + b) / 2;
868     if (streamStart <= streamEnds[m]) {
869       b = m;
870     } else {
871       a = m;
872     }
873   }
874   *streamEnd = streamEnds[b];
875   return gTrue;
876 }
877
878 Guint XRef::strToUnsigned(char *s) {
879   Guint x;
880   char *p;
881   int i;
882
883   x = 0;
884   for (p = s, i = 0; *p && isdigit(*p) && i < 10; ++p, ++i) {
885     x = 10 * x + (*p - '0');
886   }
887   return x;
888 }