1 /*
2    netrik -- The ANTRIK Internet Viewer
3    Copyright (C) Olaf D. Buddenhagen AKA antrik, et al (see AUTHORS)
4    Published under the GNU GPL; see LICENSE for details.
5 */
6 /*
7  * main.c -- you guess it ;-) : Thats's the main program.
8  *
9  * (C) 2001, 2002, 2003 antrik
10  *     2001, 2002 Patrice Neff
11  *
12  * handle startup (command line etc.); then load (and layout) first file (given
13  * as argument), and either dump it and quit immediatly (with --dump), or
14  * display it interactively and load new files (requested by user) in a loop
15  */
16 #include <curses.h>
17 #include <errno.h>
18 #include <locale.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25 
26 #include "config.h"
27 
28 #include "cfg.h"
29 #include "cmdline.h"
30 #include "colors.h"    /* for load_color_map() */
31 #include "debug.h"
32 #include "forms.h"
33 #include "form-file.h"    /* for edit_textarea() */
34 #include "interrupt.h"
35 #include "links.h"
36 #include "layout.h"
37 #include "page.h"
38 #include "pager.h"
39 #include "readline.h"
40 #include "render.h"
41 #include "screen.h"
42 #include "search.h"
43 #include "url.h"
44 
main(int argc,char * argv[])45 int main(int argc, char *argv[])
46 {
47    unsigned		cfg_len;    /* config file size */
48    int			argc_all;    /* total number of arguments in config file and command line */
49    char			**argv_all;    /* array (vector) of pointers to all config file and command line arguments */
50    char			*cfg_args;    /* array holding arguments from config file */
51 
52    int			opt_index;    /* position of first non-option parameter in (merged) command line */
53 
54    int			cur_page;    /* number of current page in page list */
55    int			page_width;
56    enum Pager_ret	pager_ret;    /* return value of pager (indicates if ended by 'q', by ':', or by following link) */
57    enum Syntax_error	syntax_err;    /* parse_syntax() found syntax errors in the HTML page */
58 
59    setlocale(LC_ALL, "");    /* enable (default) locale settings */
60 
61    /* read config file */
62    {
63       char	*home;    /* user's home directory */
64 
65       home=getenv("HOME");
66       if(home!=NULL) {
67 	 struct stat	stats;    /* config file stats */
68 	 char		cfg_name[strlen(home)+sizeof("/.netrikrc")];
69 	 snprintf(cfg_name, sizeof(cfg_name), "%s/.netrikrc", home);
70 
71 	 if(stat(cfg_name, &stats)==0) {    /* file exists, and stat succeeded */
72 	    cfg_len=(int)stats.st_size;
73 	 } else {    /* can't stat */
74 	    if(errno==ENOENT) {    /* OK, just doesn't exist */
75 	       cfg_len=0;
76 	    } else {
77 	       fprintf(stderr, "error while checking for config file (%s):", cfg_name); perror("");
78 	       exit(1);
79 	    }
80 	 }    /* can't stat */
81 
82 	 if(cfg_len) {    /* have a (nonempty) config file -> merge options from file with command line options */
83 	    FILE	*cfg_file;
84 
85 	    char	*dest_ptr;    /* current position in cfg_args */
86 	    char	*arg_start;    /* where currently scanned argument began */
87 
88 	    cfg_file=fopen(cfg_name, "r");
89 	    if(cfg_file==NULL) {
90 	       fprintf(stderr, "error: can't open config file (%s):", cfg_name); perror("");
91 	       exit(1);
92 	    }
93 
94 	    cfg_args=malloc(sizeof(char[cfg_len]));
95 	    if(cfg_args==NULL) {
96 	       fprintf(stderr, "memory allocation error while reading config file\n");
97 	       exit(1);
98 	    }
99 
100 	    if(fread(cfg_args, sizeof(char), cfg_len, cfg_file)!=cfg_len) {    /* read whole file at once */
101 	       fprintf(stderr, "error reading config file:"); perror("");
102 	       exit(1);
103 	    }
104 
105 	    if(cfg_args[cfg_len-1]!='\n') {
106 	       fprintf(stderr, "syntax error in config file: last line not terminated with newline character\n");
107 	       exit(1);
108 	    }
109 
110 	    /* store config file arguments in argument list */
111 	    for(argc_all=0, argv_all=NULL, dest_ptr=arg_start=cfg_args; dest_ptr<cfg_args+cfg_len; ++dest_ptr) {    /* scan whole config file data */
112 	       if(*dest_ptr=='\n') {    /* line end */
113 		  *dest_ptr='\0';    /* -> end argument here */
114 
115 		  /* store pointer to argument */
116 		  argv_all=realloc(argv_all, sizeof(char *[++argc_all+argc]));    /* also reserve the space for the command line arguments already */
117 		  if(argv_all==NULL) {
118 		     fprintf(stderr, "memory allocation error while reading config file\n");
119 		     exit(1);
120 		  }
121 		  argv_all[argc_all]=arg_start;    /* store (beginning with 1, not 0, as argument 0 is normally own name!) */
122 
123 		  arg_start=dest_ptr+1;    /* next argument starts after the '\n' */
124 	       }    /* line end */
125 	    }    /* for all chars in config file */
126 
127 	    /* append command line args (starting with 1st, not 0th!) after config file args */
128 	    memcpy(&argv_all[argc_all+1], argv+1, sizeof(char *[argc-1]));
129 	    argc_all+=argc;
130 	 } else {    /* no config file -> just use command line arguments */
131 	    argv_all=argv;
132 	    argc_all=argc;
133 	 }
134       } else {    /* no $HOME */
135 	 fprintf(stderr, "error: can't find your home directory (while looking for config file)\n");
136 	 exit(1);
137       }
138 
139 #ifdef DEBUG
140       if(cfg.debug) {
141 	 int	arg;
142 
143 	 debug_printf("arguments:");
144 	 for(arg=1; arg<argc_all; ++arg)
145 	    debug_printf(" %s", argv_all[arg]);
146 	 debug_printf("\n");
147       }
148 #endif
149    }
150 
151 /* Patrice --> */
152    /* process arguments */
153    opt_index=config_cmdln(argc_all, argv_all);
154    if(opt_index==argc_all) {    /* no non-option arguments */
155       fprintf(stderr,"usage: %1$s html-file\n   or: %1$s -          (read from stdin)\n", argv[0]);
156       exit(3);
157    }
158 /* <-- Patrice */
159 
160    if(cfg.force_colors < 0)    /* not explicitely set -> default depends on color scheme */
161       cfg.force_colors=!cfg.bright_background;    /* force colors if dark color scheme to be used */
162 
163    load_color_map();
164 
165    if(cfg.term_width)
166       page_width=init_curses();
167    else {
168       init_curses();
169       page_width=80;
170    }
171 
172    init_int();
173 
174    cur_page=load_page(NULL, argv_all[argc_all-1], NULL, NULL, page_width, &syntax_err);    /* load URL given in last command line argument */
175 
176 #ifdef DEBUG
177    if(cfg_len) {    /* have read config file -> need to clean up */
178       DMSG(("\nfreeing memory used by argument list...\n"));
179       free(cfg_args);
180       free(argv_all);
181    }
182 #endif
183 
184    if(!cfg.dump) {
185       start_curses();
186       endwin();    /* return to scroll mode for now, as we might want print something still before actually starting pager */
187 
188       if(syntax_err
189 #ifdef DEBUG
190          || cfg.debug
191 #endif
192       ) {
193 	 syntax_err=SE_NO;
194 	 fprintf(stderr, "\nHit some key to start viewer.\n");
195 	 cbreak(); getchar(); endwin();    /* wait for *single* keypress (endwin() resets normal scroll mode terminal settings) */
196       }
197 
198       do {    /* until quit */
199 	 int	pager_wait=0;    /* wait for keypress before returning to pager */
200 
201          pager_ret=display(page_list.page[cur_page]);    /* start pager with current page (returns when 'q' pressed, when command line mode entered with ':', or when link followed) */
202 
203 	 switch(pager_ret) {
204 	    /* pager ended by ':' */
205 	    case RET_COMMAND: {
206 	       char	*command;    /* user input */
207 
208 	       command=read_line(":");
209 	       if(command!=NULL && *command) {    /* nonempty (any command entered) */
210 		  add_history(command);
211 
212 		  if(strncmp(command, "e ", 2)==0 || strncmp(command, "E ", 2)==0) {    /* load file */
213 		     const char	*url=command+2;    /* name begins after "e " */
214 
215 		     char	*blank_pos;    /* first blank in url */
216 
217 		     /* remove trailing blank(s) */
218 		     blank_pos=strchr(url, ' ');    /* find first blank */
219 		     if(blank_pos!=NULL)    /* anything found */
220 			*blank_pos=0;    /* end string at first blank */
221 
222 		     DMSG(("\nfreeing memory used by old page...\n"));
223 		     free_layout(page_list.page[cur_page]->layout);    /* get rid of old document */
224 
225 		     if(command[0]=='e' && page_list.page[cur_page]->url->proto.type!=PT_INTERNAL)    /* load relative URL */
226 			cur_page=load_page(page_list.page[cur_page]->url, url, NULL, NULL, page_width, &syntax_err);    /* load new page (use current page URL as base) */
227 		     else    /* load absolute URL */
228 			cur_page=load_page(NULL, url, NULL, NULL, page_width, &syntax_err);    /* load new page using absoulute URL (no base) */
229 		  } else {    /* command not "e ..." */
230 		     printf("unknown command\n");
231 		     pager_wait=1;
232 		  }    /* command not "e " */
233 
234 #ifdef DEBUG
235 		  if(cfg.debug)
236 		     pager_wait=1;
237 #endif
238 	       }    /* command nonempty */
239 	       free(command);
240 	       break;
241 	    }    /* RET_COMMAND */
242 
243 	    /* pager ended by requesting text search */
244 	    case RET_SEARCH: {
245 	       struct Page	*page=page_list.page[cur_page];
246 
247 	       char		*string;
248 
249 	       struct Item	*start_item;
250 	       int		start_pos;
251 
252 	       string=read_line("/");
253 	       if(string==NULL) {    /* aborted (^D) */
254 		  search.type=SEARCH_NO;
255 		  break;
256 	       }
257 
258 	       if(*string) {    /* nonempty -> store as new search string */
259 		  add_history(string);
260 		  if(search.string!=NULL) {
261 		     DMSG(("freeing old search string...\n"));
262 		     free(search.string);
263 		  }
264 		  search.string=string;
265 	       }
266 
267 	       if(search.string==NULL) {    /* nothing entered, and there was no previous search string either */
268 		  set_color_raw(COLOR_RED|8);
269 		  printf("No search string.\n");
270 		  reset_color_raw();
271 		  pager_wait=1;
272 
273 		  search.type=SEARCH_NO;
274 		  break;
275 	       }
276 
277 	       /* find start position for search (text item containing cursor and position inside the text) */
278 	       DMSG(("looking for search start position (cursor position: %d, %d)\n   ", page->cursor_x, page->cursor_y));
279 	       {
280 		  int	cur_item;
281 
282 		  start_item=NULL;
283 
284 		  for(cur_item=0; cur_item < page->layout->page_map[page->cursor_y].count; ++cur_item) {    /* all items in cursor line */
285 		     const struct Item	*item=page->layout->page_map[page->cursor_y].item[cur_item];
286 
287 		     DMSG(("."));
288 		     if(item->type==ITEM_TEXT && item->x_start <= page->cursor_x && item->x_end > page->cursor_x) {    /* text block, and cursor is inside */
289 			int	line_offset;    /* position of cursor relative to start of text in this line */
290 
291 			line_offset=page->cursor_x - line_pos(item, page->cursor_y);
292 			if(line_offset<0) {    /* cursor before start of text in this line */
293 			   DMSG(("cursor before text line -> moving\n   "));
294 			   line_offset=-1;    /* -> assume it stands just before first char in line, so search will begin at line start */
295 			}
296 
297 			if(line_offset < line_end(item, page->cursor_y) - line_start(item, page->cursor_y)) {    /* text in this line doesn't end before cursor position -> begin search here */
298 			   DMSG(("cursor in text line\n"));
299 			   start_item=(struct Item *)item;
300 			   start_pos=line_start(item, page->cursor_y) + line_offset + 1;    /* begin search after cursor position */
301 			   break;
302 			} else if(item->y_end > page->cursor_y) {    /* cursor after end of line, but not last line in text block -> begin search with next line */
303 			   DMSG(("cursor after current text line, but in text block\n"));
304 			   start_item=(struct Item *)item;
305 			   start_pos=line_end(item, page->cursor_y);    /* begin search at beginning of next line */
306 			   break;
307 			}
308 		     }    /* cursor in text block */
309 		  }    /* all items in line */
310 		  if(start_item==NULL) {    /* nothing found (cursor not inside text block) -> find first text block starting after cursor (or first on page if nothing after cursor) */
311 		     struct Item	*item;
312 
313 		     DMSG(("cursor not in text block\nsearching for first text block after cursor\n   "));
314 		     for(item=page->layout->item_tree->parent; item!=NULL; item=item->list_next) {    /* all items (in page order) */
315 			DMSG(("."));
316 			if(item->type==ITEM_TEXT) {
317 			   if(item->y_start > page->cursor_y) {    /* starts after cursor pos -> begin search at this one */
318 			      DMSG(("found a text block after cursor"));
319 			      start_item=item;
320 			      start_pos=0;
321 			      break;    /* don't search further */
322 			   }
323 			}    /* text item */
324 		     }    /* for all items */
325 		     DMSG(("\n"));
326 		  }    /* cursor not in text block */
327 	       }    /* find start position */
328 
329 	       /* perform search */
330 	       {
331 		  struct Item	*item;
332 		  char		*found_pos=NULL;
333 
334 #ifdef DEBUG
335 		  if(start_item!=NULL) {
336 		     DMSG(("start item:\n\n%s\n\n", start_item->data.string->text));
337 		     DMSG(("start pos: %d\n", start_pos));
338 		  } else
339 		     DMSG(("at page end\n"));
340 #endif
341 
342 		  DMSG(("starting search "));
343 		  for(item=start_item; item!=NULL; (item=item->list_next), start_pos=0) {    /* all items beginning with "start_item" (don't use "start_pos" except in first!) */
344 		     DMSG(("."));
345 		     if(item->type==ITEM_TEXT) {
346 			DMSG(("\""));
347 			found_pos=strstr(item->data.string->text + start_pos, search.string);
348 			if(found_pos!=NULL)
349 			   break;
350 		     }
351 		  }
352 
353 		  if(found_pos==NULL) {    /* nothing found yet -> wrap search */
354 		     struct Item	*end_item;
355 
356 		     DMSG(("\n"));
357 		     set_color_raw(COLOR_BLUE|8);
358 		     printf("Search wrapped.\n");
359 		     reset_color_raw();
360 		     fflush(stdout);
361 		     pager_wait=1;
362 
363 		     if(start_item!=NULL)    /* have a start item -> search up to start item (inclusive!) */
364 			end_item=start_item->list_next;
365 		     else    /* no start item -> search to page end (last item) */
366 			end_item=NULL;
367 
368 		     for(item=page->layout->item_tree->parent; item!=end_item; item=item->list_next) {    /* all items until back at start */
369 			DMSG(("."));
370 			if(item->type==ITEM_TEXT) {
371 			   DMSG(("\""));
372 			   found_pos=strstr(item->data.string->text, search.string);
373 			   if(found_pos!=NULL)
374 			      break;
375 			}
376 		     }
377 		  }    /* wrap search */
378 
379 		  if(found_pos!=NULL) {    /* something found -> set cursor to match position */
380 		     const int	string_offset=found_pos - item->data.string->text;    /* position of match relative to string start */
381 
382 		     int	line;
383 
384 		     DMSG(("found\n"));
385 		     DMSG(("item:\n\n%s\n\n", item->data.string->text));
386 		     DMSG(("position: %d\n", string_offset));
387 
388 		     DMSG(("determining new cursor position "));
389 		     /* find line containing match */
390 		     for(line=item->y_start; line<item->y_end; ++line) {    /* all lines in text block */
391 			DMSG(("."));
392 			if(line_end(item, line) > string_offset)    /* first line ending after match pos -> found */
393 			   break;
394 		     }
395 #ifdef DEBUG
396 		     if(line==item->y_end) {
397 			fprintf(stderr, "internal error: failed to find line containing match position\n");
398 			exit(100);
399 		     }
400 #endif
401 
402 		     page->cursor_y=line;
403 		     page->cursor_x=line_pos(item, line) + string_offset-line_start(item, line);
404 		     DMSG(("\nposition: %d, %d\n", page->cursor_x, page->cursor_y));
405 		  } else {    /* nothing found */
406 		     set_color_raw(COLOR_RED|8);
407 		     printf("No match.\n");
408 		     reset_color_raw();
409 		     pager_wait=1;
410 
411 		     search.type=SEARCH_NO;
412 		  }
413 	       }    /* perform search */
414 
415 #ifdef DEBUG
416 	       if(cfg.debug)
417 		  pager_wait=1;
418 #endif
419 	       break;
420 	    }    /* RET_SEARCH */
421 
422 	    /* pager ended by following link */
423 	    case RET_LINK: {
424 	       const struct Page	*old_page=page_list.page[cur_page];
425 	       struct Link		*link=get_link(old_page->layout, old_page->active_link);    /* link data in item tree */
426 
427 	       switch(link->form) {    /* dispatch on link/form type */
428 		  case FORM_NO: {    /* normal link (not form control) */
429 		     char	url[strlen(link->value.data)+1];
430 		     strcpy(url, link->value.data);    /* save link URL before killing old page */
431 
432 		     if(url[0]!='#') {    /* not local anchor -> load new document */
433 			DMSG(("\nfreeing memory used by old page...\n"));
434 			free_layout(old_page->layout);    /* get rid of old document */
435 			cur_page=load_page(old_page->url, url, NULL, NULL, page_width, &syntax_err);    /* load link (use current page URL as base) */
436 		     } else    /* local anchor -> keep document, just jump to anchor */
437 			cur_page=load_page(old_page->url, url, NULL, old_page, page_width, &syntax_err);    /* load link (use current page URL as base) */
438 #ifdef DEBUG
439 		     if(cfg.debug)
440 			pager_wait=1;
441 #endif
442 		     break;
443 		  }    /* FORM_NO */
444 
445 		  case FORM_TEXT:
446 		  case FORM_PASS:
447 		  case FORM_FILE: {
448 		     char	*new_value;
449 
450 		     new_value=read_line(link->form==FORM_FILE ? "file:" : "value:");
451 		     if(new_value!=NULL) {    /* not aborted -> store new value */
452 			free(link->value.data);
453 			link->value.data=new_value; link->value.size=strlen(new_value);
454 			update_form(old_page->layout, old_page->active_link);
455 		     }
456 
457 		     if(link->form==FORM_FILE && *new_value) {    /* file to upload -> check */
458 			if(access(new_value, R_OK)==-1) {
459 			   set_color_raw(COLOR_RED|8);
460 			   printf("\nWarning: Can't access file \"%s\".\n", new_value);
461 			   reset_color_raw();
462 			   pager_wait=1;
463 			}
464 		     }
465 
466 		     break;
467 		  }    /* FORM_TEXT/FORM_PASS/FORM_FILE */
468 
469 		  case FORM_HIDDEN:
470 		     break;    /* shouldn't occur... */
471 
472 		  case FORM_CHECKBOX:
473 	          case FORM_MULTIOPTION:
474 		     link->enabled=1-link->enabled;
475 		     update_form(old_page->layout, old_page->active_link);
476 		     break;
477 
478 		  case FORM_RADIO:
479 		  case FORM_OPTION: {
480 		     const struct Item	*form_item=get_form_item(old_page->layout, old_page->active_link);
481 
482 		     struct Form_handle	handle=form_start(form_item, 0);    /* handle to iterate through all form controls (unfiltered) */
483 		     struct Link	*sibling;    /* other controls in this form */
484 
485 		     /* disable any other radio buttons/options with same name */
486 		     if(link->name!=NULL)    /* belongs to some group at all... */
487 			while((sibling=form_next(&handle)) != NULL) {    /* for all form controls */
488 			   if(sibling->name!=NULL && !strcmp(sibling->name, link->name)) {    /* in same group -> disable */
489 			      if(sibling->enabled) {
490 				 sibling->enabled=0;
491 				 set_form(handle.cur_item->data.string, sibling);    /* beware -- crude hack! */
492 			      }
493 			   }
494 			}
495 
496 		     link->enabled=1;
497 		     update_form(old_page->layout, old_page->active_link);
498 
499 		     break;
500 		  }
501 
502 		  case FORM_TEXTAREA: {
503 		     char	*err=edit_textarea(&link->value, link->name);
504 		     if(err==NULL)    /* no error */
505 			update_form(old_page->layout, old_page->active_link);
506 		     else {
507 			set_color_raw(COLOR_RED|8);
508 			printf("\nEditing textarea failed: %s\n", err);
509 			reset_color_raw();
510 			pager_wait=1;
511 		     }
512 		     break;
513 		  }
514 
515 /* Patrice, antrik --> */
516 	          case FORM_SUBMIT: {
517 		     const struct Item	*form_item=get_form_item(old_page->layout, old_page->active_link);
518 
519 		     if(form_item==NULL) {    /* orphaned button (no parent form) */
520 			set_color_raw(COLOR_RED|8);
521 			printf("\nError: Submit button outside form\n");
522 			reset_color_raw();
523 			pager_wait=1;
524 			break;
525 		     }
526 
527 		     if(form_item->data.form->url!=NULL) {    /* "action" given */
528 			link->enabled=1;    /* make the used submit button successful */
529 
530 			cur_page=load_page(old_page->url, form_item->data.form->url, form_item, NULL, page_width, &syntax_err);    /* load form "action" URL (use current page URL as base); submit form data with HTTP request */
531 			DMSG(("\nfreeing memory used by old page...\n"));
532 			free_layout(old_page->layout);
533 #ifdef DEBUG
534 			if(cfg.debug)
535 			   pager_wait=1;
536 #endif
537 		     } else {    /* no "action" */
538 			set_color_raw(COLOR_RED|8);
539 			printf("\nError: Form gives no URL to submit to\n");
540 			reset_color_raw();
541 			pager_wait=1;
542 		     }
543 		     break;
544 		  }    /* FORM_SUBMIT */
545 /* <-- Patrice, antrik */
546 
547 	       }    /* switch link type */
548 
549 	       break;
550 	    }    /* RET_LINK */
551 
552 	    /* "show link URL" requested */
553 	    case RET_LINK_URL: {
554 	       const struct Page	*page=page_list.page[cur_page];
555 	       const struct Link	*link=get_link(page->layout, page->active_link);    /* link data in item tree */
556 
557 	       char			*url;
558 
559 	       if(link->form) {
560 		  const struct Item	*form_item=get_form_item(page->layout, page->active_link);
561 		  url=form_item->data.form->url;
562 	       } else
563 		  url=link->value.data;
564 
565 	       printf("\n%s URL:\n", link->form?"submit":"link");
566 	       set_color_raw(COLOR_WHITE|8);
567 	       printf("%s\n", url);
568 	       reset_color_raw();
569 	       pager_wait=1;
570 	       break;
571 	    }    /* RET_LINK_URL */
572 
573 	    /* show absolute link target URL */
574 	    case RET_ABSOLUTE_URL: {
575 	       const struct Page	*page=page_list.page[cur_page];
576 	       const struct Link	*link=get_link(page->layout, page->active_link);    /* link data in item tree */
577 
578 	       char			*url;
579 	       struct Url		*target_url;    /* effective URL of link target */
580 
581 	       if(link->form) {
582 		  const struct Item	*form_item=get_form_item(page->layout, page->active_link);
583 		  url=form_item->data.form->url;
584 	       } else
585 		  url=link->value.data;
586 
587 	       target_url=merge_urls(page->url, url, NULL);    /* (temporarily) construct target URL merged from current URL and link URL */
588 
589 	       if(target_url->proto.type!=PT_INTERNAL) {    /* could extract absolute URL */
590 		  printf("\nabsolute link target URL:\n");
591 		  set_color_raw(COLOR_WHITE|8);
592 		  printf("%s\n", target_url->full_url);
593 		  reset_color_raw();
594 	       } else {
595 		  set_color_raw(COLOR_RED|8);
596 		  printf("\ncan't get target URL\n");
597 		  reset_color_raw();
598 	       }
599 
600 	       free_url(target_url);    /* don't keep */
601 
602 	       pager_wait=1;
603 	       break;
604 	    }    /* RET_ABSOLUTE_URL */
605 
606 	    /* "show current page URL" requested */
607 	    case RET_URL: {
608 	       const struct Page	*page=page_list.page[cur_page];
609 	       const char		*url_str=page->url->full_url;
610 
611 	       if(page->url->proto.type!=PT_INTERNAL) {    /* has URL */
612 		  printf("\ncurrent page URL:\n");
613 		  set_color_raw(COLOR_WHITE|8);
614 		  printf("%s\n", url_str);
615 		  reset_color_raw();
616 	       } else    /* internal */
617 		  printf("\npage is from stdin and has no URL\n");
618 	       pager_wait=1;
619 	       break;
620 	    }    /* RET_LINK_URL */
621 
622 	    /* pager ended by going back or forward in URL history */
623 	    case RET_HISTORY: {
624 	       struct Page	*ref=page_list.page[cur_page];    /* old page descriptor, if new page is from same URL (can reuse layout data) */
625 	       int		first_page, last_page;    /* range of page list entries to check for local */
626 	       int		page;    /* currently checked page list entry */
627 
628 	       if(page_list.pos!=cur_page) {    /* not reloading same page -> check if can reuse layout data */
629 		  DMSG(("\nchecking whether we stay in same document...\n"));
630 		  if(page_list.pos>cur_page) {
631 		     first_page=cur_page;
632 		     last_page=page_list.pos;
633 		  } else {
634 		     first_page=page_list.pos;
635 		     last_page=cur_page;
636 		  }
637 		  for(page=first_page+1; page<=last_page; ++page) {    /* test all pages between old an new one for being created by jumping to local anchors */
638 		     if(!page_list.page[page]->url->local) {    /* not local -> can't reuse layout data */
639 			ref=NULL;
640 			break;
641 		     }
642 		  }
643 	       } else    /* reloading same page -> never reuse */
644 		  ref=NULL;
645 
646 	       if(ref==NULL) {    /* layout data won't be reused */
647 		  DMSG(("freeing memory used by old page...\n"));
648 		  free_layout(page_list.page[cur_page]->layout);
649 	       }
650 
651 	       DMSG(("loading new page:\n"));
652 	       cur_page=load_page(page_list.page[page_list.pos]->url, NULL, NULL, ref, page_width, &syntax_err);    /* reload page from history (using already split url) */
653 
654 #ifdef DEBUG
655 	       if(cfg.debug)
656 		  pager_wait=1;
657 #endif
658 	       break;
659 	    }    /* RET_HISTORY */
660 
661 	    /* pager ended by SIGWINCH */
662 	    case RET_WINCH:
663 	       resize(page_list.page[cur_page]->layout, COLS);
664 	       break;
665 
666 	    case RET_QUIT:
667 	       break;
668 	    case RET_NO:    /* suppress warning */
669 	       break;
670 	 }    /* switch pager_ret */
671 
672 	 if(syntax_err) {
673 	    syntax_err=SE_NO;
674 	    pager_wait=1;
675 	 }
676 
677 	 if(pager_wait) {
678 	    printf("\nHit some key to return to pager.\n"); fflush(stdout);
679 	    cbreak(); getchar(); endwin();    /* wait for *single* keypress (endwin() resets normal scroll mode terminal settings) */
680 	 }
681 
682       } while(pager_ret!=RET_QUIT);    /* until pager ended by 'q' */
683    } else {    /* cfg.dump */
684       DMSG(("layouted page:\n"));
685       dump(page_list.page[cur_page]->layout);
686    }
687 
688 #ifdef DEBUG
689    DMSG(("\nfreeing memory used by page...\n"));
690    free_layout(page_list.page[cur_page]->layout);
691 
692    DMSG(("freeing memory used by page list...\n"));
693    free_page_list();
694 
695    DMSG(("freeing memory use by search string...\n"));
696    free(search.string);
697 #endif
698 
699    return(0);
700 }
701