1 //========================================================================
2 //
3 // Link.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 <stddef.h>
16 #include <string.h>
17 #include "gmem.h"
18 #include "GString.h"
19 #include "Error.h"
20 #include "Object.h"
21 #include "Array.h"
22 #include "Dict.h"
23 #include "Link.h"
24 
25 //------------------------------------------------------------------------
26 // LinkAction
27 //------------------------------------------------------------------------
28 
parseDest(Object * obj)29 LinkAction *LinkAction::parseDest(Object *obj) {
30   LinkAction *action;
31 
32   action = new LinkGoTo(obj);
33   if (!action->isOk()) {
34     delete action;
35     return NULL;
36   }
37   return action;
38 }
39 
parseAction(Object * obj,GString * baseURI)40 LinkAction *LinkAction::parseAction(Object *obj, GString *baseURI) {
41   LinkAction *action;
42   Object obj2, obj3, obj4, obj5;
43 
44   if (!obj->isDict()) {
45     error(errSyntaxWarning, -1, "Bad annotation action");
46     return NULL;
47   }
48 
49   obj->dictLookup("S", &obj2);
50 
51   // GoTo action
52   if (obj2.isName("GoTo")) {
53     obj->dictLookup("D", &obj3);
54     action = new LinkGoTo(&obj3);
55     obj3.free();
56 
57   // GoToR action
58   } else if (obj2.isName("GoToR")) {
59     obj->dictLookup("F", &obj3);
60     obj->dictLookup("D", &obj4);
61     action = new LinkGoToR(&obj3, &obj4);
62     obj3.free();
63     obj4.free();
64 
65   // Launch action
66   } else if (obj2.isName("Launch")) {
67     action = new LinkLaunch(obj);
68 
69   // URI action
70   } else if (obj2.isName("URI")) {
71     obj->dictLookup("URI", &obj3);
72     action = new LinkURI(&obj3, baseURI);
73     obj3.free();
74 
75   // Named action
76   } else if (obj2.isName("Named")) {
77     obj->dictLookup("N", &obj3);
78     action = new LinkNamed(&obj3);
79     obj3.free();
80 
81   // Movie action
82   } else if (obj2.isName("Movie")) {
83     obj->dictLookupNF("Annot", &obj3);
84     obj->dictLookup("T", &obj4);
85     action = new LinkMovie(&obj3, &obj4);
86     obj3.free();
87     obj4.free();
88 
89   // JavaScript action
90   } else if (obj2.isName("JavaScript")) {
91     obj->dictLookup("JS", &obj3);
92     action = new LinkJavaScript(&obj3);
93     obj3.free();
94 
95   // SubmitForm action
96   } else if (obj2.isName("SubmitForm")) {
97     obj->dictLookup("F", &obj3);
98     obj->dictLookup("Fields", &obj4);
99     obj->dictLookup("Flags", &obj5);
100     action = new LinkSubmitForm(&obj3, &obj4, &obj5);
101     obj3.free();
102     obj4.free();
103     obj5.free();
104 
105   // Hide action
106   } else if (obj2.isName("Hide")) {
107     obj->dictLookupNF("T", &obj3);
108     obj->dictLookup("H", &obj4);
109     action = new LinkHide(&obj3, &obj4);
110     obj3.free();
111     obj4.free();
112 
113   // unknown action
114   } else if (obj2.isName()) {
115     action = new LinkUnknown(obj2.getName());
116 
117   // action is missing or wrong type
118   } else {
119     error(errSyntaxWarning, -1, "Bad annotation action");
120     action = NULL;
121   }
122 
123   obj2.free();
124 
125   if (action && !action->isOk()) {
126     delete action;
127     return NULL;
128   }
129   return action;
130 }
131 
getFileSpecName(Object * fileSpecObj)132 GString *LinkAction::getFileSpecName(Object *fileSpecObj) {
133   GString *name;
134   Object obj1;
135 
136   name = NULL;
137 
138   // string
139   if (fileSpecObj->isString()) {
140     name = fileSpecObj->getString()->copy();
141 
142   // dictionary
143   } else if (fileSpecObj->isDict()) {
144 #ifdef _WIN32
145     if (!fileSpecObj->dictLookup("DOS", &obj1)->isString()) {
146 #else
147     if (!fileSpecObj->dictLookup("Unix", &obj1)->isString()) {
148 #endif
149       obj1.free();
150       fileSpecObj->dictLookup("F", &obj1);
151     }
152     if (obj1.isString()) {
153       name = obj1.getString()->copy();
154     } else {
155       error(errSyntaxWarning, -1, "Illegal file spec in link");
156     }
157     obj1.free();
158 
159   // error
160   } else {
161     error(errSyntaxWarning, -1, "Illegal file spec in link");
162   }
163 
164   // system-dependent path manipulation
165   if (name) {
166 #ifdef _WIN32
167     int i, j;
168 
169     // "//...."             --> "\...."
170     // "/x/...."            --> "x:\...."
171     // "/server/share/...." --> "\\server\share\...."
172     // convert escaped slashes to slashes and unescaped slashes to backslashes
173     i = 0;
174     if (name->getChar(0) == '/') {
175       if (name->getLength() >= 2 && name->getChar(1) == '/') {
176 	name->del(0);
177 	i = 0;
178       } else if (name->getLength() >= 2 &&
179 		 ((name->getChar(1) >= 'a' && name->getChar(1) <= 'z') ||
180 		  (name->getChar(1) >= 'A' && name->getChar(1) <= 'Z')) &&
181 		 (name->getLength() == 2 || name->getChar(2) == '/')) {
182 	name->setChar(0, name->getChar(1));
183 	name->setChar(1, ':');
184 	i = 2;
185       } else {
186 	for (j = 2; j < name->getLength(); ++j) {
187 	  if (name->getChar(j-1) != '\\' &&
188 	      name->getChar(j) == '/') {
189 	    break;
190 	  }
191 	}
192 	if (j < name->getLength()) {
193 	  name->setChar(0, '\\');
194 	  name->insert(0, '\\');
195 	  i = 2;
196 	}
197       }
198     }
199     for (; i < name->getLength(); ++i) {
200       if (name->getChar(i) == '/') {
201 	name->setChar(i, '\\');
202       } else if (name->getChar(i) == '\\' &&
203 		 i+1 < name->getLength() &&
204 		 name->getChar(i+1) == '/') {
205 	name->del(i);
206       }
207     }
208 #else
209     // no manipulation needed for Unix
210 #endif
211   }
212 
213   return name;
214 }
215 
216 //------------------------------------------------------------------------
217 // LinkDest
218 //------------------------------------------------------------------------
219 
220 LinkDest::LinkDest(Array *a) {
221   Object obj1, obj2;
222 
223   // initialize fields
224   left = bottom = right = top = zoom = 0;
225   ok = gFalse;
226 
227   // get page
228   if (a->getLength() < 2) {
229     error(errSyntaxWarning, -1, "Annotation destination array is too short");
230     return;
231   }
232   a->getNF(0, &obj1);
233   if (obj1.isInt()) {
234     pageNum = obj1.getInt() + 1;
235     pageIsRef = gFalse;
236   } else if (obj1.isRef()) {
237     pageRef.num = obj1.getRefNum();
238     pageRef.gen = obj1.getRefGen();
239     pageIsRef = gTrue;
240   } else {
241     error(errSyntaxWarning, -1, "Bad annotation destination");
242     goto err2;
243   }
244   obj1.free();
245 
246   // get destination type
247   a->get(1, &obj1);
248 
249   // XYZ link
250   if (obj1.isName("XYZ")) {
251     kind = destXYZ;
252     if (a->getLength() < 3) {
253       changeLeft = gFalse;
254     } else {
255       a->get(2, &obj2);
256       if (obj2.isNull()) {
257 	changeLeft = gFalse;
258       } else if (obj2.isNum()) {
259 	changeLeft = gTrue;
260 	left = obj2.getNum();
261       } else {
262 	error(errSyntaxWarning, -1, "Bad annotation destination position");
263 	goto err1;
264       }
265       obj2.free();
266     }
267     if (a->getLength() < 4) {
268       changeTop = gFalse;
269     } else {
270       a->get(3, &obj2);
271       if (obj2.isNull()) {
272 	changeTop = gFalse;
273       } else if (obj2.isNum()) {
274 	changeTop = gTrue;
275 	top = obj2.getNum();
276       } else {
277 	error(errSyntaxWarning, -1, "Bad annotation destination position");
278 	goto err1;
279       }
280       obj2.free();
281     }
282     if (a->getLength() < 5) {
283       changeZoom = gFalse;
284     } else {
285       a->get(4, &obj2);
286       if (obj2.isNull()) {
287 	changeZoom = gFalse;
288       } else if (obj2.isNum()) {
289 	changeZoom = gTrue;
290 	zoom = obj2.getNum();
291       } else {
292 	error(errSyntaxWarning, -1, "Bad annotation destination position");
293 	goto err1;
294       }
295       obj2.free();
296     }
297 
298   // Fit link
299   } else if (obj1.isName("Fit")) {
300     if (a->getLength() < 2) {
301       error(errSyntaxWarning, -1, "Annotation destination array is too short");
302       goto err2;
303     }
304     kind = destFit;
305 
306   // FitH link
307   } else if (obj1.isName("FitH")) {
308     if (a->getLength() < 3) {
309       error(errSyntaxWarning, -1, "Annotation destination array is too short");
310       goto err2;
311     }
312     kind = destFitH;
313     if (a->get(2, &obj2)->isNum()) {
314       top = obj2.getNum();
315       changeTop = gTrue;
316     } else if (obj2.isNull()) {
317       changeTop = gFalse;
318     } else {
319       error(errSyntaxWarning, -1, "Bad annotation destination position");
320       kind = destFit;
321     }
322     obj2.free();
323 
324   // FitV link
325   } else if (obj1.isName("FitV")) {
326     if (a->getLength() < 3) {
327       error(errSyntaxWarning, -1, "Annotation destination array is too short");
328       goto err2;
329     }
330     kind = destFitV;
331     if (a->get(2, &obj2)->isNum()) {
332       left = obj2.getNum();
333       changeLeft = gTrue;
334     } else if (obj2.isNull()) {
335       changeLeft = gFalse;
336     } else {
337       error(errSyntaxWarning, -1, "Bad annotation destination position");
338       kind = destFit;
339     }
340     obj2.free();
341 
342   // FitR link
343   } else if (obj1.isName("FitR")) {
344     if (a->getLength() < 6) {
345       error(errSyntaxWarning, -1, "Annotation destination array is too short");
346       goto err2;
347     }
348     kind = destFitR;
349     if (a->get(2, &obj2)->isNum()) {
350       left = obj2.getNum();
351     } else {
352       error(errSyntaxWarning, -1, "Bad annotation destination position");
353       kind = destFit;
354     }
355     obj2.free();
356     if (!a->get(3, &obj2)->isNum()) {
357       error(errSyntaxWarning, -1, "Bad annotation destination position");
358       kind = destFit;
359     }
360     bottom = obj2.getNum();
361     obj2.free();
362     if (!a->get(4, &obj2)->isNum()) {
363       error(errSyntaxWarning, -1, "Bad annotation destination position");
364       kind = destFit;
365     }
366     right = obj2.getNum();
367     obj2.free();
368     if (!a->get(5, &obj2)->isNum()) {
369       error(errSyntaxWarning, -1, "Bad annotation destination position");
370       kind = destFit;
371     }
372     top = obj2.getNum();
373     obj2.free();
374 
375   // FitB link
376   } else if (obj1.isName("FitB")) {
377     if (a->getLength() < 2) {
378       error(errSyntaxWarning, -1, "Annotation destination array is too short");
379       goto err2;
380     }
381     kind = destFitB;
382 
383   // FitBH link
384   } else if (obj1.isName("FitBH")) {
385     if (a->getLength() < 3) {
386       error(errSyntaxWarning, -1, "Annotation destination array is too short");
387       goto err2;
388     }
389     kind = destFitBH;
390     if (a->get(2, &obj2)->isNum()) {
391       top = obj2.getNum();
392       changeTop = gTrue;
393     } else if (obj2.isNull()) {
394       changeTop = gFalse;
395     } else {
396       error(errSyntaxWarning, -1, "Bad annotation destination position");
397       kind = destFit;
398     }
399     obj2.free();
400 
401   // FitBV link
402   } else if (obj1.isName("FitBV")) {
403     if (a->getLength() < 3) {
404       error(errSyntaxWarning, -1, "Annotation destination array is too short");
405       goto err2;
406     }
407     kind = destFitBV;
408     if (a->get(2, &obj2)->isNum()) {
409       left = obj2.getNum();
410       changeLeft = gTrue;
411     } else if (obj2.isNull()) {
412       changeLeft = gFalse;
413     } else {
414       error(errSyntaxWarning, -1, "Bad annotation destination position");
415       kind = destFit;
416     }
417     obj2.free();
418 
419   // unknown link kind
420   } else {
421     error(errSyntaxWarning, -1, "Unknown annotation destination type");
422     goto err2;
423   }
424 
425   obj1.free();
426   ok = gTrue;
427   return;
428 
429  err1:
430   obj2.free();
431  err2:
432   obj1.free();
433 }
434 
435 LinkDest::LinkDest(LinkDest *dest) {
436   kind = dest->kind;
437   pageIsRef = dest->pageIsRef;
438   if (pageIsRef)
439     pageRef = dest->pageRef;
440   else
441     pageNum = dest->pageNum;
442   left = dest->left;
443   bottom = dest->bottom;
444   right = dest->right;
445   top = dest->top;
446   zoom = dest->zoom;
447   changeLeft = dest->changeLeft;
448   changeTop = dest->changeTop;
449   changeZoom = dest->changeZoom;
450   ok = gTrue;
451 }
452 
453 //------------------------------------------------------------------------
454 // LinkGoTo
455 //------------------------------------------------------------------------
456 
457 LinkGoTo::LinkGoTo(Object *destObj) {
458   dest = NULL;
459   namedDest = NULL;
460 
461   // named destination
462   if (destObj->isName()) {
463     namedDest = new GString(destObj->getName());
464   } else if (destObj->isString()) {
465     namedDest = destObj->getString()->copy();
466 
467   // destination dictionary
468   } else if (destObj->isArray()) {
469     dest = new LinkDest(destObj->getArray());
470     if (!dest->isOk()) {
471       delete dest;
472       dest = NULL;
473     }
474 
475   // error
476   } else {
477     error(errSyntaxWarning, -1, "Illegal annotation destination");
478   }
479 }
480 
481 LinkGoTo::~LinkGoTo() {
482   if (dest)
483     delete dest;
484   if (namedDest)
485     delete namedDest;
486 }
487 
488 //------------------------------------------------------------------------
489 // LinkGoToR
490 //------------------------------------------------------------------------
491 
492 LinkGoToR::LinkGoToR(Object *fileSpecObj, Object *destObj) {
493   dest = NULL;
494   namedDest = NULL;
495 
496   // get file name
497   fileName = getFileSpecName(fileSpecObj);
498 
499   // named destination
500   if (destObj->isName()) {
501     namedDest = new GString(destObj->getName());
502   } else if (destObj->isString()) {
503     namedDest = destObj->getString()->copy();
504 
505   // destination dictionary
506   } else if (destObj->isArray()) {
507     dest = new LinkDest(destObj->getArray());
508     if (!dest->isOk()) {
509       delete dest;
510       dest = NULL;
511     }
512 
513   // error
514   } else {
515     error(errSyntaxWarning, -1, "Illegal annotation destination");
516   }
517 }
518 
519 LinkGoToR::~LinkGoToR() {
520   if (fileName)
521     delete fileName;
522   if (dest)
523     delete dest;
524   if (namedDest)
525     delete namedDest;
526 }
527 
528 
529 //------------------------------------------------------------------------
530 // LinkLaunch
531 //------------------------------------------------------------------------
532 
533 LinkLaunch::LinkLaunch(Object *actionObj) {
534   Object obj1, obj2;
535 
536   fileName = NULL;
537   params = NULL;
538 
539   if (actionObj->isDict()) {
540     if (!actionObj->dictLookup("F", &obj1)->isNull()) {
541       fileName = getFileSpecName(&obj1);
542     } else {
543       obj1.free();
544 #ifdef _WIN32
545       if (actionObj->dictLookup("Win", &obj1)->isDict()) {
546 	obj1.dictLookup("F", &obj2);
547 	fileName = getFileSpecName(&obj2);
548 	obj2.free();
549 	if (obj1.dictLookup("P", &obj2)->isString()) {
550 	  params = obj2.getString()->copy();
551 	}
552 	obj2.free();
553       } else {
554 	error(errSyntaxWarning, -1, "Bad launch-type link action");
555       }
556 #else
557       //~ This hasn't been defined by Adobe yet, so assume it looks
558       //~ just like the Win dictionary until they say otherwise.
559       if (actionObj->dictLookup("Unix", &obj1)->isDict()) {
560 	obj1.dictLookup("F", &obj2);
561 	fileName = getFileSpecName(&obj2);
562 	obj2.free();
563 	if (obj1.dictLookup("P", &obj2)->isString()) {
564 	  params = obj2.getString()->copy();
565 	}
566 	obj2.free();
567       } else {
568 	error(errSyntaxWarning, -1, "Bad launch-type link action");
569       }
570 #endif
571     }
572     obj1.free();
573   }
574 }
575 
576 LinkLaunch::~LinkLaunch() {
577   if (fileName)
578     delete fileName;
579   if (params)
580     delete params;
581 }
582 
583 //------------------------------------------------------------------------
584 // LinkURI
585 //------------------------------------------------------------------------
586 
587 LinkURI::LinkURI(Object *uriObj, GString *baseURI) {
588   GString *uri2;
589   int n;
590   char c;
591 
592   uri = NULL;
593   if (uriObj->isString()) {
594     uri2 = uriObj->getString();
595     n = (int)strcspn(uri2->getCString(), "/:");
596     if (n < uri2->getLength() && uri2->getChar(n) == ':') {
597       // "http:..." etc.
598       uri = uri2->copy();
599     } else if (!uri2->cmpN("www.", 4)) {
600       // "www.[...]" without the leading "http://"
601       uri = new GString("http://");
602       uri->append(uri2);
603     } else {
604       // relative URI
605       if (baseURI) {
606 	uri = baseURI->copy();
607 	c = uri->getChar(uri->getLength() - 1);
608 	if (c != '/' && c != '?') {
609 	  uri->append('/');
610 	}
611 	if (uri2->getChar(0) == '/') {
612 	  uri->append(uri2->getCString() + 1, uri2->getLength() - 1);
613 	} else {
614 	  uri->append(uri2);
615 	}
616       } else {
617 	uri = uri2->copy();
618       }
619     }
620   } else {
621     error(errSyntaxWarning, -1, "Illegal URI-type link");
622   }
623 }
624 
625 LinkURI::~LinkURI() {
626   if (uri)
627     delete uri;
628 }
629 
630 //------------------------------------------------------------------------
631 // LinkNamed
632 //------------------------------------------------------------------------
633 
634 LinkNamed::LinkNamed(Object *nameObj) {
635   name = NULL;
636   if (nameObj->isName()) {
637     name = new GString(nameObj->getName());
638   }
639 }
640 
641 LinkNamed::~LinkNamed() {
642   if (name) {
643     delete name;
644   }
645 }
646 
647 //------------------------------------------------------------------------
648 // LinkMovie
649 //------------------------------------------------------------------------
650 
651 LinkMovie::LinkMovie(Object *annotObj, Object *titleObj) {
652   annotRef.num = -1;
653   title = NULL;
654   if (annotObj->isRef()) {
655     annotRef = annotObj->getRef();
656   } else if (titleObj->isString()) {
657     title = titleObj->getString()->copy();
658   } else {
659     error(errSyntaxError, -1,
660 	  "Movie action is missing both the Annot and T keys");
661   }
662 }
663 
664 LinkMovie::~LinkMovie() {
665   if (title) {
666     delete title;
667   }
668 }
669 
670 //------------------------------------------------------------------------
671 // LinkJavaScript
672 //------------------------------------------------------------------------
673 
674 LinkJavaScript::LinkJavaScript(Object *jsObj) {
675   char buf[4096];
676   int n;
677 
678   if (jsObj->isString()) {
679     js = jsObj->getString()->copy();
680   } else if (jsObj->isStream()) {
681     js = new GString();
682     jsObj->streamReset();
683     while ((n = jsObj->getStream()->getBlock(buf, sizeof(buf))) > 0) {
684       js->append(buf, n);
685     }
686     jsObj->streamClose();
687   } else {
688     error(errSyntaxError, -1, "JavaScript action JS key is wrong type");
689     js = NULL;
690   }
691 }
692 
693 LinkJavaScript::~LinkJavaScript() {
694   if (js) {
695     delete js;
696   }
697 }
698 
699 //------------------------------------------------------------------------
700 // LinkSubmitForm
701 //------------------------------------------------------------------------
702 
703 LinkSubmitForm::LinkSubmitForm(Object *urlObj, Object *fieldsObj,
704 			       Object *flagsObj) {
705   if (urlObj->isString()) {
706     url = urlObj->getString()->copy();
707   } else {
708     error(errSyntaxError, -1, "SubmitForm action URL is wrong type");
709     url = NULL;
710   }
711 
712   if (fieldsObj->isArray()) {
713     fieldsObj->copy(&fields);
714   } else {
715     if (!fieldsObj->isNull()) {
716       error(errSyntaxError, -1, "SubmitForm action Fields value is wrong type");
717     }
718     fields.initNull();
719   }
720 
721   if (flagsObj->isInt()) {
722     flags = flagsObj->getInt();
723   } else {
724     if (!flagsObj->isNull()) {
725       error(errSyntaxError, -1, "SubmitForm action Flags value is wrong type");
726     }
727     flags = 0;
728   }
729 }
730 
731 LinkSubmitForm::~LinkSubmitForm() {
732   if (url) {
733     delete url;
734   }
735   fields.free();
736 }
737 
738 //------------------------------------------------------------------------
739 // LinkHide
740 //------------------------------------------------------------------------
741 
742 LinkHide::LinkHide(Object *fieldsObj, Object *hideFlagObj) {
743   if (fieldsObj->isRef() || fieldsObj->isString() || fieldsObj->isArray()) {
744     fieldsObj->copy(&fields);
745   } else {
746     error(errSyntaxError, -1, "Hide action T value is wrong type");
747     fields.initNull();
748   }
749 
750   if (hideFlagObj->isBool()) {
751     hideFlag = hideFlagObj->getBool();
752   } else {
753     error(errSyntaxError, -1, "Hide action H value is wrong type");
754     hideFlag = gFalse;
755   }
756 }
757 
758 LinkHide::~LinkHide() {
759   fields.free();
760 }
761 
762 //------------------------------------------------------------------------
763 // LinkUnknown
764 //------------------------------------------------------------------------
765 
766 LinkUnknown::LinkUnknown(char *actionA) {
767   action = new GString(actionA);
768 }
769 
770 LinkUnknown::~LinkUnknown() {
771   delete action;
772 }
773 
774 //------------------------------------------------------------------------
775 // Link
776 //------------------------------------------------------------------------
777 
778 Link::Link(Dict *dict, GString *baseURI) {
779   Object obj1, obj2;
780   double t;
781 
782   action = NULL;
783   ok = gFalse;
784 
785   // get rectangle
786   if (!dict->lookup("Rect", &obj1)->isArray()) {
787     error(errSyntaxError, -1, "Annotation rectangle is wrong type");
788     goto err2;
789   }
790   if (!obj1.arrayGet(0, &obj2)->isNum()) {
791     error(errSyntaxError, -1, "Bad annotation rectangle");
792     goto err1;
793   }
794   x1 = obj2.getNum();
795   obj2.free();
796   if (!obj1.arrayGet(1, &obj2)->isNum()) {
797     error(errSyntaxError, -1, "Bad annotation rectangle");
798     goto err1;
799   }
800   y1 = obj2.getNum();
801   obj2.free();
802   if (!obj1.arrayGet(2, &obj2)->isNum()) {
803     error(errSyntaxError, -1, "Bad annotation rectangle");
804     goto err1;
805   }
806   x2 = obj2.getNum();
807   obj2.free();
808   if (!obj1.arrayGet(3, &obj2)->isNum()) {
809     error(errSyntaxError, -1, "Bad annotation rectangle");
810     goto err1;
811   }
812   y2 = obj2.getNum();
813   obj2.free();
814   obj1.free();
815   if (x1 > x2) {
816     t = x1;
817     x1 = x2;
818     x2 = t;
819   }
820   if (y1 > y2) {
821     t = y1;
822     y1 = y2;
823     y2 = t;
824   }
825 
826   // look for destination
827   if (!dict->lookup("Dest", &obj1)->isNull()) {
828     action = LinkAction::parseDest(&obj1);
829 
830   // look for action
831   } else {
832     obj1.free();
833     if (dict->lookup("A", &obj1)->isDict()) {
834       action = LinkAction::parseAction(&obj1, baseURI);
835     }
836   }
837   obj1.free();
838 
839   // check for bad action
840   if (action) {
841     ok = gTrue;
842   }
843 
844   return;
845 
846  err1:
847   obj2.free();
848  err2:
849   obj1.free();
850 }
851 
852 Link::~Link() {
853   if (action) {
854     delete action;
855   }
856 }
857 
858 //------------------------------------------------------------------------
859 // Links
860 //------------------------------------------------------------------------
861 
862 Links::Links(Object *annots, GString *baseURI) {
863   Link *link;
864   Object obj1, obj2, obj3;
865   int size;
866   int i;
867 
868   links = NULL;
869   size = 0;
870   numLinks = 0;
871 
872   if (annots->isArray()) {
873     for (i = 0; i < annots->arrayGetLength(); ++i) {
874       if (annots->arrayGet(i, &obj1)->isDict()) {
875 	obj1.dictLookup("Subtype", &obj2);
876 	obj1.dictLookup("FT", &obj3);
877 	if (obj2.isName("Link") ||
878 	    (obj2.isName("Widget") && (obj3.isName("Btn") || obj3.isNull()))) {
879 	  link = new Link(obj1.getDict(), baseURI);
880 	  if (link->isOk()) {
881 	    if (numLinks >= size) {
882 	      size += 16;
883 	      links = (Link **)greallocn(links, size, sizeof(Link *));
884 	    }
885 	    links[numLinks++] = link;
886 	  } else {
887 	    delete link;
888 	  }
889 	}
890 	obj3.free();
891 	obj2.free();
892       }
893       obj1.free();
894     }
895   }
896 }
897 
898 Links::~Links() {
899   int i;
900 
901   for (i = 0; i < numLinks; ++i)
902     delete links[i];
903   gfree(links);
904 }
905 
906 LinkAction *Links::find(double x, double y) {
907   int i;
908 
909   for (i = numLinks - 1; i >= 0; --i) {
910     if (links[i]->inRect(x, y)) {
911       return links[i]->getAction();
912     }
913   }
914   return NULL;
915 }
916 
917 GBool Links::onLink(double x, double y) {
918   int i;
919 
920   for (i = 0; i < numLinks; ++i) {
921     if (links[i]->inRect(x, y))
922       return gTrue;
923   }
924   return gFalse;
925 }
926