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