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