1 /*
2  * $LynxId: LYHistory.c,v 1.85 2013/04/30 22:20:30 tom Exp $
3  */
4 #include <HTUtils.h>
5 #include <HTTP.h>
6 #include <GridText.h>
7 #include <HTAlert.h>
8 #include <HText.h>
9 #include <LYGlobalDefs.h>
10 #include <LYUtils.h>
11 #include <LYHistory.h>
12 #include <LYPrint.h>
13 #include <LYDownload.h>
14 #include <LYOptions.h>
15 #include <LYKeymap.h>
16 #include <LYList.h>
17 #include <LYShowInfo.h>
18 #include <LYStrings.h>
19 #include <LYCharUtils.h>
20 #include <LYCharSets.h>
21 #include <LYrcFile.h>
22 #ifdef DISP_PARTIAL
23 #include <LYMainLoop.h>
24 #endif
25 
26 #ifdef DIRED_SUPPORT
27 #include <LYUpload.h>
28 #include <LYLocal.h>
29 #endif /* DIRED_SUPPORT */
30 
31 #include <LYexit.h>
32 #include <LYLeaks.h>
33 #include <HTCJK.h>
34 
35 HTList *Visited_Links = NULL;	/* List of safe popped docs. */
36 int Visited_Links_As = VISITED_LINKS_AS_LATEST | VISITED_LINKS_REVERSE;
37 
38 static VisitedLink *PrevVisitedLink = NULL;	/* NULL on auxillary */
39 static VisitedLink *PrevActiveVisitedLink = NULL;	/* Last non-auxillary */
40 static VisitedLink Latest_first;
41 static VisitedLink Latest_last;
42 static VisitedLink *Latest_tree;
43 static VisitedLink *First_tree;
44 static VisitedLink *Last_by_first;
45 
46 int nhist_extra;
47 
48 #ifdef LY_FIND_LEAKS
49 static int already_registered_free_messages_stack = 0;
50 static int already_registered_clean_all_history = 0;
51 #endif
52 
53 #ifdef LY_FIND_LEAKS
54 /*
55  * Utility for freeing the list of visited links.  - FM
56  */
Visited_Links_free(void)57 static void Visited_Links_free(void)
58 {
59     VisitedLink *vl;
60     HTList *cur = Visited_Links;
61 
62     PrevVisitedLink = NULL;
63     PrevActiveVisitedLink = NULL;
64     if (!cur)
65 	return;
66 
67     while (NULL != (vl = (VisitedLink *) HTList_nextObject(cur))) {
68 	FREE(vl->address);
69 	FREE(vl->title);
70 	FREE(vl);
71     }
72     HTList_delete(Visited_Links);
73     Visited_Links = NULL;
74     Latest_last.prev_latest = &Latest_first;
75     Latest_first.next_latest = &Latest_last;
76     Last_by_first = Latest_tree = First_tree = 0;
77     return;
78 }
79 #endif /* LY_FIND_LEAKS */
80 
81 #ifdef DEBUG
trace_history(const char * tag)82 static void trace_history(const char *tag)
83 {
84     if (TRACE) {
85 	CTRACE((tfp, "HISTORY %s %d/%d (%d extra)\n",
86 		tag, nhist, size_history, nhist_extra));
87 	CTRACE_FLUSH(tfp);
88     }
89 }
90 #else
91 #define trace_history(tag)	/* nothing */
92 #endif /* DEBUG */
93 
94 /*
95  * Utility for listing visited links, making any repeated links the most
96  * current in the list.  - FM
97  */
LYAddVisitedLink(DocInfo * doc)98 void LYAddVisitedLink(DocInfo *doc)
99 {
100     VisitedLink *tmp;
101     HTList *cur;
102     const char *title = (doc->title ? doc->title : NO_TITLE);
103 
104     if (isEmpty(doc->address)) {
105 	PrevVisitedLink = NULL;
106 	return;
107     }
108 
109     /*
110      * Exclude POST or HEAD replies, and bookmark, menu or list files.  - FM
111      */
112     if (doc->post_data || doc->isHEAD || doc->bookmark ||
113 	(			/* special url or a temp file */
114 	    (!StrNCmp(doc->address, "LYNX", 4) ||
115 	     !StrNCmp(doc->address, "file://localhost/", 17)))) {
116 	int related = 1;	/* First approximation only */
117 
118 	if (LYIsUIPage(doc->address, UIP_HISTORY) ||
119 	    LYIsUIPage(doc->address, UIP_VLINKS) ||
120 	    LYIsUIPage(doc->address, UIP_SHOWINFO) ||
121 	    isLYNXMESSAGES(doc->address) ||
122 	    ((related = 0) != 0) ||
123 #ifdef DIRED_SUPPORT
124 	    LYIsUIPage(doc->address, UIP_DIRED_MENU) ||
125 	    LYIsUIPage(doc->address, UIP_UPLOAD_OPTIONS) ||
126 	    LYIsUIPage(doc->address, UIP_PERMIT_OPTIONS) ||
127 #endif /* DIRED_SUPPORT */
128 	    LYIsUIPage(doc->address, UIP_PRINT_OPTIONS) ||
129 	    LYIsUIPage(doc->address, UIP_DOWNLOAD_OPTIONS) ||
130 	    LYIsUIPage(doc->address, UIP_OPTIONS_MENU) ||
131 	    isLYNXKEYMAP(doc->address) ||
132 	    LYIsUIPage(doc->address, UIP_LIST_PAGE) ||
133 #ifdef USE_ADDRLIST_PAGE
134 	    LYIsUIPage(doc->address, UIP_ADDRLIST_PAGE) ||
135 #endif
136 	    LYIsUIPage(doc->address, UIP_CONFIG_DEF) ||
137 	    LYIsUIPage(doc->address, UIP_LYNXCFG) ||
138 	    isLYNXCOOKIE(doc->address) ||
139 	    LYIsUIPage(doc->address, UIP_TRACELOG)) {
140 	    if (!related)
141 		PrevVisitedLink = NULL;
142 	    return;
143 	}
144     }
145 
146     if (!Visited_Links) {
147 	Visited_Links = HTList_new();
148 #ifdef LY_FIND_LEAKS
149 	atexit(Visited_Links_free);
150 #endif
151 	Latest_last.prev_latest = &Latest_first;
152 	Latest_first.next_latest = &Latest_last;
153 	Latest_last.next_latest = NULL;		/* Find bugs quick! */
154 	Latest_first.prev_latest = NULL;
155 	Last_by_first = Latest_tree = First_tree = NULL;
156     }
157 
158     cur = Visited_Links;
159     while (NULL != (tmp = (VisitedLink *) HTList_nextObject(cur))) {
160 	if (!strcmp(NonNull(tmp->address),
161 		    NonNull(doc->address))) {
162 	    PrevVisitedLink = PrevActiveVisitedLink = tmp;
163 	    /* Already visited.  Update the last-visited info. */
164 	    if (tmp->next_latest == &Latest_last)	/* optimization */
165 		return;
166 
167 	    /* Remove from "latest" chain */
168 	    tmp->prev_latest->next_latest = tmp->next_latest;
169 	    tmp->next_latest->prev_latest = tmp->prev_latest;
170 
171 	    /* Insert at the end of the "latest" chain */
172 	    Latest_last.prev_latest->next_latest = tmp;
173 	    tmp->prev_latest = Latest_last.prev_latest;
174 	    tmp->next_latest = &Latest_last;
175 	    Latest_last.prev_latest = tmp;
176 	    return;
177 	}
178     }
179 
180     if ((tmp = typecalloc(VisitedLink)) == NULL)
181 	outofmem(__FILE__, "LYAddVisitedLink");
182 
183     assert(tmp != NULL);
184 
185     StrAllocCopy(tmp->address, doc->address);
186     LYformTitle(&(tmp->title), title);
187 
188     /* First-visited chain */
189     HTList_appendObject(Visited_Links, tmp);	/* At end */
190     tmp->prev_first = Last_by_first;
191     Last_by_first = tmp;
192 
193     /* Tree structure */
194     if (PrevVisitedLink) {
195 	VisitedLink *a = PrevVisitedLink;
196 	VisitedLink *b = a->next_tree;
197 	int l = PrevVisitedLink->level;
198 
199 	/* Find last on the deeper levels */
200 	while (b && b->level > l)
201 	    a = b, b = b->next_tree;
202 
203 	if (!b)			/* a == Latest_tree */
204 	    Latest_tree = tmp;
205 	tmp->next_tree = a->next_tree;
206 	a->next_tree = tmp;
207 
208 	tmp->level = PrevVisitedLink->level + 1;
209     } else {
210 	if (Latest_tree)
211 	    Latest_tree->next_tree = tmp;
212 	tmp->level = 0;
213 	tmp->next_tree = NULL;
214 	Latest_tree = tmp;
215     }
216     PrevVisitedLink = PrevActiveVisitedLink = tmp;
217     if (!First_tree)
218 	First_tree = tmp;
219 
220     /* "latest" chain */
221     Latest_last.prev_latest->next_latest = tmp;
222     tmp->prev_latest = Latest_last.prev_latest;
223     tmp->next_latest = &Latest_last;
224     Latest_last.prev_latest = tmp;
225 
226     return;
227 }
228 
229 /*
230  * Returns true if this is a page that we would push onto the stack if not
231  * forced.  If docurl is NULL, only the title is considered; otherwise also
232  * check the URL whether it is (likely to be) a generated special page.
233  */
LYwouldPush(const char * title,const char * docurl)234 BOOLEAN LYwouldPush(const char *title,
235 		    const char *docurl)
236 {
237     BOOLEAN rc = FALSE;
238 
239     /*
240      * All non-pushable generated pages have URLs that begin with
241      * "file://localhost/" and end with HTML_SUFFIX.  - kw
242      */
243     if (docurl) {
244 	size_t ulen;
245 
246 	if (StrNCmp(docurl, "file://localhost/", 17) != 0 ||
247 	    (ulen = strlen(docurl)) <= strlen(HTML_SUFFIX) ||
248 	    strcmp(docurl + ulen - strlen(HTML_SUFFIX), HTML_SUFFIX) != 0) {
249 	    /*
250 	     * If it is not a local HTML file, it may be a Web page that
251 	     * accidentally has the same title.  So return TRUE now.  - kw
252 	     */
253 	    return TRUE;
254 	}
255     }
256 
257     if (docurl) {
258 	rc = (BOOLEAN)
259 	    !(LYIsUIPage(docurl, UIP_HISTORY)
260 	      || LYIsUIPage(docurl, UIP_PRINT_OPTIONS)
261 #ifdef DIRED_SUPPORT
262 	      || LYIsUIPage(docurl, UIP_DIRED_MENU)
263 	      || LYIsUIPage(docurl, UIP_UPLOAD_OPTIONS)
264 	      || LYIsUIPage(docurl, UIP_PERMIT_OPTIONS)
265 #endif /* DIRED_SUPPORT */
266 	    );
267     } else {
268 	rc = (BOOLEAN)
269 	    !(!strcmp(title, HISTORY_PAGE_TITLE)
270 	      || !strcmp(title, PRINT_OPTIONS_TITLE)
271 #ifdef DIRED_SUPPORT
272 	      || !strcmp(title, DIRED_MENU_TITLE)
273 	      || !strcmp(title, UPLOAD_OPTIONS_TITLE)
274 	      || !strcmp(title, PERMIT_OPTIONS_TITLE)
275 #endif /* DIRED_SUPPORT */
276 	    );
277     }
278     return rc;
279 }
280 
281 /*
282  * Free post-data for 'DocInfo'
283  */
LYFreePostData(DocInfo * doc)284 void LYFreePostData(DocInfo *doc)
285 {
286     BStrFree(doc->post_data);
287     FREE(doc->post_content_type);
288 }
289 
290 /*
291  * Free strings associated with a 'DocInfo' struct.
292  */
LYFreeDocInfo(DocInfo * doc)293 void LYFreeDocInfo(DocInfo *doc)
294 {
295     FREE(doc->title);
296     FREE(doc->address);
297     FREE(doc->bookmark);
298     LYFreePostData(doc);
299 }
300 
301 /*
302  * Free the information in the last history entry.
303  */
clean_extra_history(void)304 static void clean_extra_history(void)
305 {
306     trace_history("clean_extra_history");
307     nhist += nhist_extra;
308     while (nhist_extra > 0) {
309 	nhist--;
310 	LYFreeDocInfo(&HDOC(nhist));
311 	nhist_extra--;
312     }
313     trace_history("...clean_extra_history");
314 }
315 
316 /*
317  * Free the entire history stack, for auditing memory leaks.
318  */
319 #ifdef LY_FIND_LEAKS
clean_all_history(void)320 static void clean_all_history(void)
321 {
322     trace_history("clean_all_history");
323     clean_extra_history();
324     while (nhist > 0) {
325 	nhist--;
326 	LYFreeDocInfo(&HDOC(nhist));
327     }
328     trace_history("...clean_all_history");
329 }
330 #endif
331 
332 /* FIXME What is the relationship to are_different() from the mainloop?! */
are_identical(HistInfo * doc,DocInfo * doc1)333 static int are_identical(HistInfo * doc, DocInfo *doc1)
334 {
335     return (STREQ(doc1->address, doc->hdoc.address)
336 	    && BINEQ(doc1->post_data, doc->hdoc.post_data)
337 	    && !strcmp(NonNull(doc1->bookmark),
338 		       NonNull(doc->hdoc.bookmark))
339 	    && doc1->isHEAD == doc->hdoc.isHEAD);
340 }
341 
LYAllocHistory(int entries)342 void LYAllocHistory(int entries)
343 {
344     CTRACE((tfp, "LYAllocHistory %d vs %d\n", entries, size_history));
345     if (entries + 1 >= size_history) {
346 	unsigned want;
347 	int save = size_history;
348 
349 	size_history = (entries + 2) * 2;
350 	want = (unsigned) size_history *(unsigned) sizeof(*history);
351 
352 	if (history == 0) {
353 	    history = typeMallocn(HistInfo, want);
354 	} else {
355 	    history = typeRealloc(HistInfo, history, want);
356 	}
357 	if (history == 0)
358 	    outofmem(__FILE__, "LYAllocHistory");
359 
360 	assert(history != NULL);
361 
362 	while (save < size_history) {
363 	    memset(&history[save++], 0, sizeof(history[0]));
364 	}
365     }
366     CTRACE((tfp, "...LYAllocHistory %d vs %d\n", entries, size_history));
367 }
368 
369 /*
370  * Push the current filename, link and line number onto the history list.
371  */
LYpush(DocInfo * doc,int force_push)372 int LYpush(DocInfo *doc, int force_push)
373 {
374     /*
375      * Don't push NULL file names.
376      */
377     if (*doc->address == '\0')
378 	return 0;
379 
380     /*
381      * Check whether this is a document we don't push unless forced.  - FM
382      */
383     if (!force_push) {
384 	/*
385 	 * Don't push the history, printer, or download lists.
386 	 */
387 	if (!LYwouldPush(doc->title, doc->address)) {
388 	    if (!LYforce_no_cache)
389 		LYoverride_no_cache = TRUE;
390 	    return 0;
391 	}
392     }
393 
394     /*
395      * If file is identical to one before it, don't push it.
396      * But do not duplicate it if there is only one on the stack,
397      * note that HDOC() starts from 0, so nhist should be > 0.
398      */
399     if (nhist >= 1 && are_identical(&(history[nhist - 1]), doc)) {
400 	if (HDOC(nhist - 1).internal_link == doc->internal_link) {
401 	    /* But it is nice to have the last position remembered!
402 	       - kw */
403 	    HDOC(nhist - 1).link = doc->link;
404 	    HDOC(nhist - 1).line = doc->line;
405 	    return 0;
406 	}
407     }
408 
409     /*
410      * If file is identical to the current document, just move the pointer.
411      */
412     if (nhist_extra >= 1 && are_identical(&(history[nhist]), doc)) {
413 	HDOC(nhist).link = doc->link;
414 	HDOC(nhist).line = doc->line;
415 	nhist_extra--;
416 	LYAllocHistory(nhist);
417 	nhist++;
418 	trace_history("LYpush: just move the cursor");
419 	return 1;
420     }
421 
422     clean_extra_history();
423 #ifdef LY_FIND_LEAKS
424     if (!already_registered_clean_all_history) {
425 	already_registered_clean_all_history = 1;
426 	atexit(clean_all_history);
427     }
428 #endif
429 
430     /*
431      * OK, push it...
432      */
433     LYAllocHistory(nhist);
434     HDOC(nhist).link = doc->link;
435     HDOC(nhist).line = doc->line;
436 
437     HDOC(nhist).title = NULL;
438     LYformTitle(&(HDOC(nhist).title), doc->title);
439 
440     HDOC(nhist).address = NULL;
441     StrAllocCopy(HDOC(nhist).address, doc->address);
442 
443     HDOC(nhist).post_data = NULL;
444     BStrCopy(HDOC(nhist).post_data, doc->post_data);
445 
446     HDOC(nhist).post_content_type = NULL;
447     StrAllocCopy(HDOC(nhist).post_content_type, doc->post_content_type);
448 
449     HDOC(nhist).bookmark = NULL;
450     StrAllocCopy(HDOC(nhist).bookmark, doc->bookmark);
451 
452     HDOC(nhist).isHEAD = doc->isHEAD;
453     HDOC(nhist).safe = doc->safe;
454 
455     HDOC(nhist).internal_link = FALSE;	/* by default */
456     history[nhist].intern_seq_start = -1;	/* by default */
457     if (doc->internal_link) {
458 	/* Now some tricky stuff: if the caller thinks that the doc
459 	   to push was the result of following an internal
460 	   (fragment) link, we check whether we believe it.
461 	   It is only accepted as valid if the immediately preceding
462 	   item on the history stack is actually the same document
463 	   except for fragment and location info.  I.e. the Parent
464 	   Anchors are the same.
465 	   Also of course this requires that this is not the first
466 	   history item. - kw */
467 	if (nhist > 0) {
468 	    DocAddress WWWDoc;
469 	    HTParentAnchor *thisparent, *thatparent = NULL;
470 
471 	    WWWDoc.address = doc->address;
472 	    WWWDoc.post_data = doc->post_data;
473 	    WWWDoc.post_content_type = doc->post_content_type;
474 	    WWWDoc.bookmark = doc->bookmark;
475 	    WWWDoc.isHEAD = doc->isHEAD;
476 	    WWWDoc.safe = doc->safe;
477 	    thisparent =
478 		HTAnchor_findAddress(&WWWDoc);
479 	    /* Now find the ParentAnchor for the previous history
480 	     * item - kw
481 	     */
482 	    if (thisparent) {
483 		/* If the last-pushed item is a LYNXIMGMAP but THIS one
484 		 * isn't, compare the physical URLs instead. - kw
485 		 */
486 		if (isLYNXIMGMAP(HDOC(nhist - 1).address) &&
487 		    !isLYNXIMGMAP(doc->address)) {
488 		    WWWDoc.address = HDOC(nhist - 1).address + LEN_LYNXIMGMAP;
489 		    /*
490 		     * If THIS item is a LYNXIMGMAP but the last-pushed one
491 		     * isn't, fake it by using THIS item's address for
492 		     * thatparent... - kw
493 		     */
494 		} else if (isLYNXIMGMAP(doc->address) &&
495 			   !isLYNXIMGMAP(HDOC(nhist - 1).address)) {
496 		    char *temp = NULL;
497 
498 		    StrAllocCopy(temp, STR_LYNXIMGMAP);
499 		    StrAllocCat(temp, doc->address + LEN_LYNXIMGMAP);
500 		    WWWDoc.address = temp;
501 		    WWWDoc.post_content_type = HDOC(nhist - 1).post_content_type;
502 		    WWWDoc.bookmark = HDOC(nhist - 1).bookmark;
503 		    WWWDoc.isHEAD = HDOC(nhist - 1).isHEAD;
504 		    WWWDoc.safe = HDOC(nhist - 1).safe;
505 		    thatparent =
506 			HTAnchor_findAddress(&WWWDoc);
507 		    FREE(temp);
508 		} else {
509 		    WWWDoc.address = HDOC(nhist - 1).address;
510 		}
511 		if (!thatparent) {	/* if not yet done */
512 		    WWWDoc.post_data = HDOC(nhist - 1).post_data;
513 		    WWWDoc.post_content_type = HDOC(nhist - 1).post_content_type;
514 		    WWWDoc.bookmark = HDOC(nhist - 1).bookmark;
515 		    WWWDoc.isHEAD = HDOC(nhist - 1).isHEAD;
516 		    WWWDoc.safe = HDOC(nhist - 1).safe;
517 		    thatparent =
518 			HTAnchor_findAddress(&WWWDoc);
519 		}
520 		/* In addition to equality of the ParentAnchors, require
521 		 * that IF we have a HTMainText (i.e., it wasn't just
522 		 * HTuncache'd by mainloop), THEN it has to be consistent
523 		 * with what we are trying to push.
524 		 *
525 		 * This may be overkill...  - kw
526 		 */
527 		if (thatparent == thisparent &&
528 		    (!HTMainText || HTMainAnchor == thisparent)
529 		    ) {
530 		    HDOC(nhist).internal_link = TRUE;
531 		    history[nhist].intern_seq_start =
532 			history[nhist - 1].intern_seq_start >= 0 ?
533 			history[nhist - 1].intern_seq_start : nhist - 1;
534 		    CTRACE((tfp, "\nLYpush: pushed as internal link, OK\n"));
535 		}
536 	    }
537 	}
538 	if (!HDOC(nhist).internal_link) {
539 	    CTRACE((tfp, "\nLYpush: push as internal link requested, %s\n",
540 		    "but didn't check out!"));
541 	}
542     }
543     CTRACE((tfp, "\nLYpush[%d]: address:%s\n        title:%s\n",
544 	    nhist, doc->address, doc->title));
545     nhist++;
546     return 1;
547 }
548 
549 /*
550  * Pop the previous filename, link and line number from the history list.
551  */
LYpop(DocInfo * doc)552 void LYpop(DocInfo *doc)
553 {
554     if (nhist > 0) {
555 	clean_extra_history();
556 	nhist--;
557 
558 	LYFreeDocInfo(doc);
559 
560 	*doc = HDOC(nhist);
561 
562 #ifdef DISP_PARTIAL
563 	/* assume we pop the 'doc' to show it soon... */
564 	LYSetNewline(doc->line);	/* reinitialize */
565 #endif /* DISP_PARTIAL */
566 	CTRACE((tfp, "LYpop[%d]: address:%s\n     title:%s\n",
567 		nhist, doc->address, doc->title));
568     }
569 }
570 
571 /*
572  * Move to the previous filename, link and line number from the history list.
573  */
LYhist_prev(DocInfo * doc)574 void LYhist_prev(DocInfo *doc)
575 {
576     trace_history("LYhist_prev");
577     if (nhist > 0 && (nhist_extra || nhist < size_history)) {
578 	nhist--;
579 	nhist_extra++;
580 	LYpop_num(nhist, doc);
581 	trace_history("...LYhist_prev");
582     }
583 }
584 
585 /*
586  * Called before calling LYhist_prev().
587  */
LYhist_prev_register(DocInfo * doc)588 void LYhist_prev_register(DocInfo *doc)
589 {
590     trace_history("LYhist_prev_register");
591     if (nhist > 1) {
592 	if (nhist_extra) {	/* Make something to return back */
593 	    /* Store the new position */
594 	    HDOC(nhist).link = doc->link;
595 	    HDOC(nhist).line = doc->line;
596 	} else if (LYpush(doc, 0)) {
597 	    nhist--;
598 	    nhist_extra++;
599 	}
600 	trace_history("...LYhist_prev_register");
601     }
602 }
603 
604 /*
605  * Move to the next filename, link and line number from the history.
606  */
LYhist_next(DocInfo * doc,DocInfo * newdoc)607 int LYhist_next(DocInfo *doc, DocInfo *newdoc)
608 {
609     if (nhist_extra <= 1)	/* == 1 when we are the last one */
610 	return 0;
611     /* Store the new position */
612     HDOC(nhist).link = doc->link;
613     HDOC(nhist).line = doc->line;
614     LYAllocHistory(nhist);
615     nhist++;
616     nhist_extra--;
617     LYpop_num(nhist, newdoc);
618     return 1;
619 }
620 
621 /*
622  * Pop the specified hist entry, link and line number from the history list but
623  * don't actually remove the entry, just return it.
624  * (This procedure is badly named :)
625  */
LYpop_num(int number,DocInfo * doc)626 void LYpop_num(int number,
627 	       DocInfo *doc)
628 {
629     if (number >= 0 && nhist + nhist_extra > number) {
630 	doc->link = HDOC(number).link;
631 	doc->line = HDOC(number).line;
632 	StrAllocCopy(doc->title, HDOC(number).title);
633 	StrAllocCopy(doc->address, HDOC(number).address);
634 	BStrCopy(doc->post_data, HDOC(number).post_data);
635 	StrAllocCopy(doc->post_content_type, HDOC(number).post_content_type);
636 	StrAllocCopy(doc->bookmark, HDOC(number).bookmark);
637 	doc->isHEAD = HDOC(number).isHEAD;
638 	doc->safe = HDOC(number).safe;
639 	doc->internal_link = HDOC(number).internal_link;	/* ?? */
640 #ifdef DISP_PARTIAL
641 	/* assume we pop the 'doc' to show it soon... */
642 	LYSetNewline(doc->line);	/* reinitialize */
643 #endif /* DISP_PARTIAL */
644 	if (TRACE) {
645 	    CTRACE((tfp, "LYpop_num(%d)\n", number));
646 	    CTRACE((tfp, "  link    %d\n", doc->link));
647 	    CTRACE((tfp, "  line    %d\n", doc->line));
648 	    CTRACE((tfp, "  title   %s\n", NonNull(doc->title)));
649 	    CTRACE((tfp, "  address %s\n", NonNull(doc->address)));
650 	}
651     }
652 }
653 
654 /*
655  * This procedure outputs the history buffer into a temporary file.
656  */
showhistory(char ** newfile)657 int showhistory(char **newfile)
658 {
659     static char tempfile[LY_MAXPATH] = "\0";
660     char *Title = NULL;
661     int x = 0;
662     FILE *fp0;
663 
664     if ((fp0 = InternalPageFP(tempfile, TRUE)) == 0)
665 	return (-1);
666 
667     LYLocalFileToURL(newfile, tempfile);
668 
669     LYforce_HTML_mode = TRUE;	/* force this file to be HTML */
670     LYforce_no_cache = TRUE;	/* force this file to be new */
671 
672     BeginInternalPage(fp0, HISTORY_PAGE_TITLE, HISTORY_PAGE_HELP);
673 
674     fprintf(fp0, "<p align=right> <a href=\"%s\">[%s]</a>\n",
675 	    STR_LYNXMESSAGES, STATUSLINES_TITLE);
676 
677     fprintf(fp0, "<pre>\n");
678 
679     fprintf(fp0, "<em>%s</em>\n", gettext("You selected:"));
680     for (x = nhist + nhist_extra - 1; x >= 0; x--) {
681 	/*
682 	 * The number of the document in the hist stack, its title in a link,
683 	 * and its address.  - FM
684 	 */
685 	if (HDOC(x).title != NULL) {
686 	    StrAllocCopy(Title, HDOC(x).title);
687 	    LYEntify(&Title, TRUE);
688 	    LYTrimLeading(Title);
689 	    LYTrimTrailing(Title);
690 	    if (*Title == '\0')
691 		StrAllocCopy(Title, NO_TITLE);
692 	} else {
693 	    StrAllocCopy(Title, NO_TITLE);
694 	}
695 	fprintf(fp0,
696 		"%s<em>%d</em>. <tab id=t%d><a href=\"%s%d\">%s</a>\n",
697 		(x > 99 ? "" : x < 10 ? "  " : " "),
698 		x, x, STR_LYNXHIST, x, Title);
699 	if (HDOC(x).address != NULL) {
700 	    StrAllocCopy(Title, HDOC(x).address);
701 	    LYEntify(&Title, TRUE);
702 	} else {
703 	    StrAllocCopy(Title, gettext("(no address)"));
704 	}
705 	if (HDOC(x).internal_link) {
706 	    if (history[x].intern_seq_start == history[nhist - 1].intern_seq_start)
707 		StrAllocCat(Title, gettext(" (internal)"));
708 	    else
709 		StrAllocCat(Title, gettext(" (was internal)"));
710 	}
711 	fprintf(fp0, "<tab to=t%d>%s\n", x, Title);
712     }
713     fprintf(fp0, "</pre>\n");
714     EndInternalPage(fp0);
715 
716     LYCloseTempFP(fp0);
717     FREE(Title);
718     return (0);
719 }
720 
721 /*
722  * This function makes the history page seem like any other type of file since
723  * more info is needed than can be provided by the normal link structure.  We
724  * saved out the history number to a special URL.
725  *
726  * The info looks like:  LYNXHIST:#
727  */
historytarget(DocInfo * newdoc)728 BOOLEAN historytarget(DocInfo *newdoc)
729 {
730     int number;
731     DocAddress WWWDoc;
732     HTParentAnchor *tmpanchor;
733     HText *text;
734     BOOLEAN treat_as_intern = FALSE;
735 
736     if ((!newdoc || !newdoc->address) ||
737 	strlen(newdoc->address) < 10 || !isdigit(UCH(*(newdoc->address + 9))))
738 	return (FALSE);
739 
740     if ((number = atoi(newdoc->address + 9)) > nhist + nhist_extra || number < 0)
741 	return (FALSE);
742 
743     /*
744      * Optimization: assume we came from the History Page,
745      * so never return back - always a new version next time.
746      * But check first whether HTMainText is really the History
747      * Page document - in some obscure situations this may not be
748      * the case.  If HTMainText seems to be a History Page document,
749      * also check that it really hasn't been pushed. - LP, kw
750      */
751     if (HTMainText && nhist > 0 &&
752 	!strcmp(HTLoadedDocumentTitle(), HISTORY_PAGE_TITLE) &&
753 	LYIsUIPage3(HTLoadedDocumentURL(), UIP_HISTORY, 0) &&
754 	strcmp(HTLoadedDocumentURL(), HDOC(nhist - 1).address)) {
755 	HTuncache_current_document();	/* don't waste the cache */
756     }
757 
758     LYpop_num(number, newdoc);
759     if (((newdoc->internal_link &&
760 	  history[number].intern_seq_start == history[nhist - 1].intern_seq_start)
761 	 || (number < nhist - 1 &&
762 	     HDOC(nhist - 1).internal_link &&
763 	     number == history[nhist - 1].intern_seq_start))
764 	&& !(LYforce_no_cache == TRUE && LYoverride_no_cache == FALSE)) {
765 	if (track_internal_links) {
766 	    LYforce_no_cache = FALSE;
767 	    LYinternal_flag = TRUE;
768 	    newdoc->internal_link = TRUE;
769 	    treat_as_intern = TRUE;
770 	}
771     } else {
772 	newdoc->internal_link = FALSE;
773     }
774     /*
775      * If we have POST content, and have LYresubmit_posts set or have no_cache
776      * set or do not still have the text cached, ask the user whether to
777      * resubmit the form.  - FM
778      */
779     if (newdoc->post_data != NULL) {
780 	WWWDoc.address = newdoc->address;
781 	WWWDoc.post_data = newdoc->post_data;
782 	WWWDoc.post_content_type = newdoc->post_content_type;
783 	WWWDoc.bookmark = newdoc->bookmark;
784 	WWWDoc.isHEAD = newdoc->isHEAD;
785 	WWWDoc.safe = newdoc->safe;
786 	tmpanchor = HTAnchor_findAddress(&WWWDoc);
787 	text = (HText *) HTAnchor_document(tmpanchor);
788 	if (((((LYresubmit_posts == TRUE) ||
789 	       (LYforce_no_cache == TRUE &&
790 		LYoverride_no_cache == FALSE)) &&
791 	      !(treat_as_intern && !reloading)) ||
792 	     text == NULL) &&
793 	    (isLYNXIMGMAP(newdoc->address) ||
794 	     HTConfirm(CONFIRM_POST_RESUBMISSION) == TRUE)) {
795 	    LYforce_no_cache = TRUE;
796 	    LYoverride_no_cache = FALSE;
797 	} else if (text != NULL) {
798 	    LYforce_no_cache = FALSE;
799 	    LYoverride_no_cache = TRUE;
800 	} else {
801 	    HTInfoMsg(CANCELLED);
802 	    return (FALSE);
803 	}
804     }
805 
806     if (number != 0)
807 	StrAllocCat(newdoc->title, gettext(" (From History)"));
808     return (TRUE);
809 }
810 
811 /*
812  * This procedure outputs the Visited Links list into a temporary file.  - FM
813  * Returns link's number to make active (1-based), or 0 if not required.
814  */
LYShowVisitedLinks(char ** newfile)815 int LYShowVisitedLinks(char **newfile)
816 {
817     static char tempfile[LY_MAXPATH] = "\0";
818     char *Title = NULL;
819     char *Address = NULL;
820     int x, tot;
821     FILE *fp0;
822     VisitedLink *vl;
823     HTList *cur = Visited_Links;
824     int offset;
825     int ret = 0;
826     const char *arrow, *post_arrow;
827 
828     if (!cur)
829 	return (-1);
830 
831     if ((fp0 = InternalPageFP(tempfile, TRUE)) == 0)
832 	return (-1);
833 
834     LYLocalFileToURL(newfile, tempfile);
835     LYRegisterUIPage(*newfile, UIP_VLINKS);
836 
837     LYforce_HTML_mode = TRUE;	/* force this file to be HTML */
838     LYforce_no_cache = TRUE;	/* force this file to be new */
839 
840     BeginInternalPage(fp0, VISITED_LINKS_TITLE, VISITED_LINKS_HELP);
841 
842 #ifndef NO_OPTION_FORMS
843     fprintf(fp0, "<form action=\"%s\" method=\"post\">\n", STR_LYNXOPTIONS);
844     LYMenuVisitedLinks(fp0, FALSE);
845     fprintf(fp0, "<input type=\"submit\" value=\"Accept Changes\">\n");
846     fprintf(fp0, "</form>\n");
847     fprintf(fp0, "<P>\n");
848 #endif
849 
850     fprintf(fp0, "<pre>\n");
851     fprintf(fp0, "<em>%s</em>\n",
852 	    gettext("You visited (POSTs, bookmark, menu and list files excluded):"));
853     if (Visited_Links_As & VISITED_LINKS_REVERSE)
854 	tot = x = HTList_count(Visited_Links);
855     else
856 	tot = x = -1;
857 
858     if (Visited_Links_As & VISITED_LINKS_AS_TREE) {
859 	vl = First_tree;
860     } else if (Visited_Links_As & VISITED_LINKS_AS_LATEST) {
861 	if (Visited_Links_As & VISITED_LINKS_REVERSE)
862 	    vl = Latest_last.prev_latest;
863 	else
864 	    vl = Latest_first.next_latest;
865 	if (vl == &Latest_last || vl == &Latest_first)
866 	    vl = NULL;
867     } else {
868 	if (Visited_Links_As & VISITED_LINKS_REVERSE)
869 	    vl = Last_by_first;
870 	else
871 	    vl = (VisitedLink *) HTList_nextObject(cur);
872     }
873     while (NULL != vl) {
874 	/*
875 	 * The number of the document (most recent highest), its title in a
876 	 * link, and its address.  - FM
877 	 */
878 	post_arrow = arrow = "";
879 	if (Visited_Links_As & VISITED_LINKS_REVERSE)
880 	    x--;
881 	else
882 	    x++;
883 	if (vl == PrevActiveVisitedLink) {
884 	    if (Visited_Links_As & VISITED_LINKS_REVERSE)
885 		ret = tot - x + 2;
886 	    else
887 		ret = x + 3;
888 	}
889 	if (vl == PrevActiveVisitedLink) {
890 	    post_arrow = "<A NAME=current></A>";
891 	    /* Otherwise levels 0 and 1 look the same when with arrow: */
892 	    arrow = (vl->level && (Visited_Links_As & VISITED_LINKS_AS_TREE))
893 		? "==>" : "=>";
894 	    StrAllocCat(*newfile, "#current");
895 	}
896 	if (Visited_Links_As & VISITED_LINKS_AS_TREE) {
897 	    offset = 2 * vl->level;
898 	    if (offset > 24)
899 		offset = (offset + 24) / 2;
900 	    if (offset > LYcols * 3 / 4)
901 		offset = LYcols * 3 / 4;
902 	} else
903 	    offset = (x > 99 ? 0 : x < 10 ? 2 : 1);
904 	if (non_empty(vl->title)) {
905 	    StrAllocCopy(Title, vl->title);
906 	    LYEntify(&Title, TRUE);
907 	    LYTrimLeading(Title);
908 	    LYTrimTrailing(Title);
909 	    if (*Title == '\0')
910 		StrAllocCopy(Title, NO_TITLE);
911 	} else {
912 	    StrAllocCopy(Title, NO_TITLE);
913 	}
914 	if (non_empty(vl->address)) {
915 	    StrAllocCopy(Address, vl->address);
916 	    LYEntify(&Address, FALSE);
917 	    fprintf(fp0,
918 		    "%-*s%s<em>%d</em>. <tab id=t%d><a href=\"%s\">%s</a>\n",
919 		    offset, arrow, post_arrow,
920 		    x, x, Address, Title);
921 	} else {
922 	    fprintf(fp0,
923 		    "%-*s%s<em>%d</em>. <tab id=t%d><em>%s</em>\n",
924 		    offset, arrow, post_arrow,
925 		    x, x, Title);
926 	}
927 	if (Address != NULL) {
928 	    StrAllocCopy(Address, vl->address);
929 	    LYEntify(&Address, TRUE);
930 	}
931 	fprintf(fp0, "<tab to=t%d>%s\n", x,
932 		((Address != NULL) ? Address : gettext("(no address)")));
933 	if (Visited_Links_As & VISITED_LINKS_AS_TREE)
934 	    vl = vl->next_tree;
935 	else if (Visited_Links_As & VISITED_LINKS_AS_LATEST) {
936 	    if (Visited_Links_As & VISITED_LINKS_REVERSE)
937 		vl = vl->prev_latest;
938 	    else
939 		vl = vl->next_latest;
940 	    if (vl == &Latest_last || vl == &Latest_first)
941 		vl = NULL;
942 	} else {
943 	    if (Visited_Links_As & VISITED_LINKS_REVERSE)
944 		vl = vl->prev_first;
945 	    else
946 		vl = (VisitedLink *) HTList_nextObject(cur);
947 	}
948     }
949     fprintf(fp0, "</pre>\n");
950     EndInternalPage(fp0);
951 
952     LYCloseTempFP(fp0);
953     FREE(Title);
954     FREE(Address);
955     return (ret);
956 }
957 
958 /*
959  * Keep cycled buffer for statusline messages.
960  * But allow user to change how big it will be from userdefs.h
961  */
962 #ifndef STATUSBUFSIZE
963 #define STATUSBUFSIZE   40
964 #endif
965 
966 int status_buf_size = STATUSBUFSIZE;
967 
968 static char **buffstack;
969 static int topOfStack = 0;
970 
971 #ifdef LY_FIND_LEAKS
free_messages_stack(void)972 static void free_messages_stack(void)
973 {
974     if (buffstack != 0) {
975 	topOfStack = status_buf_size;
976 
977 	while (--topOfStack >= 0) {
978 	    FREE(buffstack[topOfStack]);
979 	}
980 	FREE(buffstack);
981     }
982 }
983 #endif
984 
to_stack(char * str)985 static void to_stack(char *str)
986 {
987     /*
988      * Cycle buffer:
989      */
990     if (topOfStack >= status_buf_size) {
991 	topOfStack = 0;
992     }
993 
994     /*
995      * Register string.
996      */
997     if (buffstack == 0)
998 	buffstack = typecallocn(char *, (size_t) status_buf_size);
999 
1000     FREE(buffstack[topOfStack]);
1001     buffstack[topOfStack] = str;
1002     topOfStack++;
1003 #ifdef LY_FIND_LEAKS
1004     if (!already_registered_free_messages_stack) {
1005 	already_registered_free_messages_stack = 1;
1006 	atexit(free_messages_stack);
1007     }
1008 #endif
1009     if (topOfStack >= status_buf_size) {
1010 	topOfStack = 0;
1011     }
1012 }
1013 
1014 /*
1015  * Dump statusline messages into the buffer.
1016  * Called from mainloop() when exit immediately with an error:
1017  * can not access startfile (first_file) so a couple of alert messages
1018  * will be very useful on exit.
1019  * (Don't expect everyone will look a trace log in case of difficulties:))
1020  */
LYstatusline_messages_on_exit(char ** buf)1021 void LYstatusline_messages_on_exit(char **buf)
1022 {
1023     int i;
1024 
1025     if (buffstack != 0) {
1026 	StrAllocCat(*buf, "\n");
1027 	/* print messages in chronological order:
1028 	 * probably a single message but let's do it.
1029 	 */
1030 	i = topOfStack - 1;
1031 	while (++i < status_buf_size) {
1032 	    if (buffstack[i] != NULL) {
1033 		StrAllocCat(*buf, buffstack[i]);
1034 		StrAllocCat(*buf, "\n");
1035 	    }
1036 	}
1037 	i = -1;
1038 	while (++i < topOfStack) {
1039 	    if (buffstack[i] != NULL) {
1040 		StrAllocCat(*buf, buffstack[i]);
1041 		StrAllocCat(*buf, "\n");
1042 	    }
1043 	}
1044     }
1045 }
1046 
LYstore_message2(const char * message,const char * argument)1047 void LYstore_message2(const char *message,
1048 		      const char *argument)
1049 {
1050 
1051     if (message != NULL) {
1052 	char *temp = NULL;
1053 
1054 	HTSprintf0(&temp, message, NonNull(argument));
1055 	to_stack(temp);
1056     }
1057 }
1058 
LYstore_message(const char * message)1059 void LYstore_message(const char *message)
1060 {
1061     if (message != NULL) {
1062 	char *temp = NULL;
1063 
1064 	StrAllocCopy(temp, message);
1065 	to_stack(temp);
1066     }
1067 }
1068 
1069 /*     LYLoadMESSAGES
1070  *     --------------
1071  *     Create a text/html stream with a list of recent statusline messages.
1072  *     LYNXMESSAGES:/ internal page.
1073  *     [implementation based on LYLoadKeymap()].
1074  */
1075 
LYLoadMESSAGES(const char * arg GCC_UNUSED,HTParentAnchor * anAnchor,HTFormat format_out,HTStream * sink)1076 static int LYLoadMESSAGES(const char *arg GCC_UNUSED,
1077 			  HTParentAnchor *anAnchor,
1078 			  HTFormat format_out,
1079 			  HTStream *sink)
1080 {
1081     HTFormat format_in = WWW_HTML;
1082     HTStream *target = NULL;
1083     char *buf = NULL;
1084     int nummsg = 0;
1085 
1086     int i;
1087     char *temp = NULL;
1088 
1089     if (buffstack != 0) {
1090 	i = status_buf_size;
1091 	while (--i >= 0) {
1092 	    if (buffstack[i] != NULL)
1093 		nummsg++;
1094 	}
1095     }
1096 
1097     /*
1098      * Set up the stream.  - FM
1099      */
1100     target = HTStreamStack(format_in, format_out, sink, anAnchor);
1101 
1102     if (!target || target == NULL) {
1103 	HTSprintf0(&buf, CANNOT_CONVERT_I_TO_O,
1104 		   HTAtom_name(format_in), HTAtom_name(format_out));
1105 	HTAlert(buf);
1106 	FREE(buf);
1107 	return (HT_NOT_LOADED);
1108     }
1109     anAnchor->no_cache = TRUE;
1110 
1111 #define PUTS(buf)    (*target->isa->put_block)(target, buf, (int) strlen(buf))
1112 
1113     HTSprintf0(&buf, "<html>\n<head>\n");
1114     PUTS(buf);
1115     /*
1116      * This page is a list of messages in display character set.
1117      */
1118     HTSprintf0(&buf, "<META %s content=\"text/html;charset=%s\">\n",
1119 	       "http-equiv=\"content-type\"",
1120 	       LYCharSet_UC[current_char_set].MIMEname);
1121     PUTS(buf);
1122     HTSprintf0(&buf, "<title>%s</title>\n</head>\n<body>\n",
1123 	       STATUSLINES_TITLE);
1124     PUTS(buf);
1125 
1126     if (nummsg != 0) {
1127 	HTSprintf0(&buf, "<ol>\n");
1128 	PUTS(buf);
1129 	/* print messages in reverse order: */
1130 	i = topOfStack;
1131 	while (--i >= 0) {
1132 	    if (buffstack[i] != NULL) {
1133 		StrAllocCopy(temp, buffstack[i]);
1134 		LYEntify(&temp, TRUE);
1135 		HTSprintf0(&buf, "<li value=%d> <em>%s</em>\n", nummsg, temp);
1136 		nummsg--;
1137 		PUTS(buf);
1138 	    }
1139 	}
1140 	i = status_buf_size;
1141 	while (--i >= topOfStack) {
1142 	    if (buffstack[i] != NULL) {
1143 		StrAllocCopy(temp, buffstack[i]);
1144 		LYEntify(&temp, TRUE);
1145 		HTSprintf0(&buf, "<li value=%d> <em>%s</em>\n", nummsg, temp);
1146 		nummsg--;
1147 		PUTS(buf);
1148 	    }
1149 	}
1150 	FREE(temp);
1151 	HTSprintf0(&buf, "</ol>\n</body>\n</html>\n");
1152     } else {
1153 	HTSprintf0(&buf, "<p>%s\n</body>\n</html>\n",
1154 		   gettext("(No messages yet)"));
1155     }
1156     PUTS(buf);
1157 
1158     (*target->isa->_free) (target);
1159     FREE(buf);
1160     return (HT_LOADED);
1161 }
1162 
1163 #ifdef GLOBALDEF_IS_MACRO
1164 #define _LYMESSAGES_C_GLOBALDEF_1_INIT { "LYNXMESSAGES", LYLoadMESSAGES, 0}
1165 GLOBALDEF(HTProtocol, LYLynxStatusMessages, _LYMESSAGES_C_GLOBALDEF_1_INIT);
1166 #else
1167 GLOBALDEF HTProtocol LYLynxStatusMessages =
1168 {"LYNXMESSAGES", LYLoadMESSAGES, 0};
1169 #endif /* GLOBALDEF_IS_MACRO */
1170