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