1 /*
2 * Copyright (C) 1997 Michael R. Elkins <me@cs.hmc.edu>
3 * Copyright (C) 2012 Michael R. Elkins <me@sigpipe.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 *
19 *
20 * Created: Thu Dec 4 02:13:11 PST 1997
21 * Last Modified: Tue Jul 4 11:23:49 CEST 2000
22 */
23
24 #ifdef USE_SLANG
25 #include <slcurses.h>
26 #include <sys/file.h>
27 #else
28 #ifdef HAVE_NCURSES_H
29 #include <ncurses.h>
30 #else
31 #include <curses.h>
32 #endif
33 #endif /* USE_SLANG */
34
35 #include <pwd.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <ctype.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <fcntl.h>
42 #include <sys/stat.h>
43
44 #ifdef HAVE_REGEX_H
45 #include <regex.h>
46 #else
47 #include <rx/rxposix.h>
48 #endif
49
50 #include "quote.h"
51
52 #define DEFAULT_REGEXP "(((http|https|ftp|gopher)|mailto):(//)?[^ <>\"\t]*|(www|ftp)[0-9]?\\.[-a-z0-9.]+)[^ .,;\t\n\r<\">\\):]?[^, <>\"\t]*[^ .,;\t\n\r<\">\\):]"
53 #define DEFAULT_COMMAND "firefox %s"
54 #define SYSTEM_INITFILE "/usr/local/etc/urlview/system.urlview"
55
56 #define OFFSET 2
57 #define PAGELEN (LINES - 1 - OFFSET)
58 #define URLSIZE 128
59
60 enum
61 {
62 FULL = 1,
63 INDEX,
64 MOTION
65 };
66
67 extern int mutt_enter_string (unsigned char *buf, size_t buflen, int y, int x,
68 int flags);
69
search_forward(char * search,int urlcount,char ** url,int * redraw,int * current,int * top)70 void search_forward (char *search, int urlcount, char **url, int *redraw, int *current, int *top)
71 {
72 regex_t rx;
73 int i, j;
74
75 if (strlen(search) == 0 || *search == '\n')
76 {
77 move (LINES - 1, 0);
78 clrtoeol ();
79 *redraw = MOTION;
80 }
81 else
82 {
83 if ( (j = regcomp (&rx, search, REG_EXTENDED | REG_ICASE | REG_NEWLINE)) )
84 {
85 regerror (j, &rx, search, sizeof (search));
86 regfree (&rx);
87 puts (search);
88 }
89 for (i = *current + 1; i < urlcount; i++)
90 {
91 if (regexec (&rx, url[i], 0, NULL, 0) == 0)
92 {
93 *current = i;
94 if (*current < *top || *current > *top + PAGELEN -1)
95 {
96 *top = *current - *current % PAGELEN - 1;
97 }
98 i = urlcount;
99 }
100 }
101 move (LINES - 1, 0);
102 clrtoeol ();
103 *redraw = INDEX;
104 regfree (&rx);
105 }
106 }
107
search_backward(char * search,int urlcount,char ** url,int * redraw,int * current,int * top)108 void search_backward (char *search, int urlcount, char **url, int *redraw, int *current, int *top)
109 {
110 regex_t rx;
111 int i, j;
112
113 (void)urlcount; /*unused*/
114 if (strlen(search) == 0 || *search == '\n')
115 {
116 move (LINES - 1, 0);
117 clrtoeol ();
118 *redraw = MOTION;
119 }
120 else
121 {
122 if ((j = regcomp (&rx, search, REG_EXTENDED | REG_ICASE | REG_NEWLINE)))
123 {
124 regerror (j, &rx, search, sizeof (search));
125 regfree (&rx);
126 puts (search);
127 }
128 for (i = *current - 1; i >= 0; i--)
129 {
130 if (regexec (&rx, url[i], 0, NULL, 0) == 0)
131 {
132 *current = i;
133 if (*current < *top || *current > *top + PAGELEN -1)
134 {
135 *top = *current - *current % PAGELEN - 1;
136 if (*top < 0)
137 *top = 0;
138 }
139 i = 0;
140 }
141 }
142 move (LINES - 1, 0);
143 clrtoeol ();
144 *redraw = INDEX;
145 regfree (&rx);
146 }
147 }
148
main(int argc,char ** argv)149 int main (int argc, char **argv)
150 {
151 struct passwd *pw;
152 struct stat stat_buf;
153 #ifndef USE_SLANG
154 SCREEN *scr;
155 #endif
156 FILE *fp;
157 regex_t rx;
158 regmatch_t match;
159 char buf[1024];
160 char command[1024];
161 char regexp[1024];
162 char search[1024];
163 char scratch[1024];
164 char wrapchoice[1024];
165 char **url;
166 int urlsize = URLSIZE;
167 char *pc;
168 char *wc;
169 size_t len;
170 size_t offset;
171 int i;
172 int top = 0;
173 int startline = 0;
174 int oldcurrent = 0;
175 int current = 0;
176 int done = 0;
177 int quitonlaunch = 0;
178 int redraw = FULL;
179 int urlcount = 0;
180 int urlcheck = 0;
181 int reopen_tty = 0;
182 int is_filter = 0;
183
184 int expert = 0;
185
186 int skip_browser = 0;
187
188 int menu_wrapping = 0;
189
190 if (argc == 1)
191 is_filter = 1;
192
193 strncpy (regexp, DEFAULT_REGEXP, sizeof (regexp) - 1);
194 strncpy (command, DEFAULT_COMMAND, sizeof (command) - 1);
195
196 /*** read the initialization file ***/
197
198 pw = getpwuid (getuid ());
199 snprintf (buf, sizeof (buf), "%s/.urlview", pw->pw_dir);
200
201 /*** Check for users rc-file ***/
202 if (stat (buf,&stat_buf) == -1)
203 snprintf (buf, sizeof(buf), "%s", SYSTEM_INITFILE);
204
205 if ((fp = fopen (buf, "r")))
206 {
207 while (fgets (buf, sizeof (buf), fp) != NULL)
208 {
209 if (buf[0] == '#' || buf[0] == '\n')
210 continue;
211 if (strncmp ("REGEXP", buf, 6) == 0 && isspace (buf[6]))
212 {
213 pc = buf + 6;
214 while (isspace (*pc))
215 pc++;
216 wc = regexp;
217 while (*pc && *pc != '\n')
218 {
219 if (*pc == '\\')
220 {
221 pc++;
222 switch (*pc)
223 {
224 case 'n':
225 *wc++ = '\n';
226 break;
227 case 'r':
228 *wc++ = '\r';
229 break;
230 case 't':
231 *wc++ = '\t';
232 break;
233 case 'f':
234 *wc++ = '\f';
235 break;
236 default:
237 *wc++ = '\\';
238 *wc++ = *pc;
239 break;
240 }
241 }
242 else
243 *wc++ = *pc;
244 pc++;
245 }
246 *wc = 0;
247 }
248 else if (strncmp ("COMMAND", buf, 7) == 0 && isspace (buf[7]))
249 {
250 pc = buf + 7;
251 while (isspace (*pc))
252 pc++;
253 pc[ strlen (pc) - 1 ] = 0; /* kill the trailing newline */
254 strncpy (command, pc, sizeof (command) - 1);
255 command[sizeof (command) - 1] = 0;
256 skip_browser = 1;
257 }
258 else if (strncmp ("WRAP", buf, 4) == 0 && isspace (buf[4]))
259 {
260 pc = buf + 4;
261 while (isspace (*pc))
262 pc++;
263 pc[ strlen (pc) - 1 ] = 0; /* kill the trailing newline */
264 strncpy (wrapchoice, pc, sizeof (wrapchoice) - 1);
265 wrapchoice[sizeof (wrapchoice) - 1] = 0;
266 /* checking the value, case insensitive */
267 if (strcasecmp("YES", wrapchoice) == 0)
268 menu_wrapping = 1;
269 else if (!strcasecmp("NO", wrapchoice) == 0)
270 {
271 printf ("Unknown value for WRAP: %s. Valid values are: YES, NO\n", wrapchoice);
272 exit (1);
273 }
274 }
275 else if (strncmp ("BROWSER", buf, 7) == 0 && isspace (buf[7]))
276 {
277 skip_browser = 0;
278 }
279 else if (strcmp ("EXPERT\n", buf) == 0)
280 {
281 expert = 1;
282 }
283 else if (strcmp ("QUITONLAUNCH\n", buf) == 0)
284 {
285 quitonlaunch = 1;
286 }
287 else
288 {
289 printf ("Unknown command: %s", buf);
290 exit (1);
291 }
292 }
293 fclose (fp);
294 }
295
296 /* Only use the $BROWSER environment variable if
297 * (a) no COMMAND in rc file or
298 * (b) BROWSER in rc file.
299 * If both COMMAND and BROWSER are in the rc file, then the option used
300 * last counts.
301 */
302 if (!skip_browser) {
303 pc = getenv("BROWSER");
304 if (pc)
305 {
306 if (strlen(pc) > 0) {
307 strncpy (command, pc, sizeof (command) - 1);
308 } else {
309 printf("Your $BROWSER is zero-length. Falling back to COMMAND.");
310 }
311 }
312 }
313
314 if (!expert && strchr (command, '\''))
315 {
316 puts ("\n\
317 ERROR: Your $BROWSER contains a single\n\
318 quote (') character. This is most likely\n\
319 in error; please read the manual page\n\
320 for details. If you really want to use\n\
321 this command, please put the word EXPERT\n\
322 into a line of its own in your \n\
323 ~/.urlview file.\n\
324 ");
325 exit (1);
326 }
327
328 /*** compile the regexp ***/
329
330 if ((i = regcomp (&rx, regexp, REG_EXTENDED | REG_ICASE | REG_NEWLINE)))
331 {
332 regerror (i, &rx, buf, sizeof (buf));
333 regfree (&rx);
334 puts (buf);
335 exit (1);
336 }
337
338 /*** find matching patterns ***/
339
340 if ((url = (char **) malloc (urlsize * sizeof (char *))) == NULL)
341 {
342 printf ("Couldn't allocate memory for url list\n");
343 exit (1);
344 }
345
346 for (; is_filter || argv[optind]; optind++)
347 {
348 if (is_filter || strcmp ("-", argv[optind]) == 0)
349 {
350 fp = stdin;
351 reopen_tty = 1;
352 }
353 else if (!is_filter && argv[optind][0] == '-') {
354 startline = atoi(argv[optind]+1);
355 current = -1;
356 continue;
357 }
358 else if (!(fp = fopen (argv[optind], "r")))
359 {
360 perror (argv[optind]);
361 continue;
362 }
363
364 while (fgets (buf, sizeof (buf) - 1, fp) != NULL)
365 {
366 --startline;
367 offset = 0;
368 while (regexec (&rx, buf + offset, 1, &match, offset ? REG_NOTBOL : 0) == 0)
369 {
370 len = match.rm_eo - match.rm_so;
371 if (urlcount >= urlsize)
372 {
373 void *urltmp;
374 urltmp = realloc ((void *) url, (urlsize + URLSIZE) * sizeof (char *));
375 if (urltmp == NULL)
376 {
377 printf ("Couldn't allocate memory for additional urls, "
378 "only first %d displayed\n", urlsize);
379 goto got_urls;
380 }
381 else
382 {
383 urlsize += URLSIZE;
384 url = (char **) urltmp;
385 }
386 }
387 url[urlcount] = malloc (len + 1);
388 memcpy (url[urlcount], buf + match.rm_so + offset, len);
389 url[urlcount][len] = 0;
390 for (urlcheck=0; urlcheck < urlcount; urlcheck++)
391 {
392 if(strcasecmp(url[urlcount],url[urlcheck])==0)
393 {
394 urlcount--;
395 break;
396 }
397 }
398 if (current < 0 && startline <= 0)
399 current = urlcount;
400 urlcount++;
401 offset += match.rm_eo;
402 }
403 }
404 got_urls:
405 fclose (fp);
406 if (is_filter)
407 break;
408 }
409
410 regfree (&rx);
411
412 if (!urlcount)
413 {
414 puts ("No URLs found.");
415 exit (1);
416 }
417
418 if (current < 0)
419 current = urlcount - 1;
420
421 /*** present the URLs to the user ***/
422
423 #ifdef USE_SLANG
424 if (reopen_tty) {
425 SLang_TT_Read_FD = open ("/dev/tty", O_RDONLY);
426 if(SLang_TT_Read_FD < 0) {
427 perror("Can't open /dev/tty");
428 exit(1);
429 }
430 initscr ();
431 #else
432 /* if we piped a file we can't use initscr() because it assumes `stdin' */
433 fp = reopen_tty ? fopen ("/dev/tty", "r") : stdin;
434 if(fp == NULL) {
435 perror("Can't open /dev/tty");
436 exit(1);
437 }
438 scr = newterm (NULL, stdout, fp);
439 set_term (scr);
440 #endif
441
442 cbreak ();
443 noecho ();
444 #ifdef HAVE_CURS_SET
445 curs_set (1);
446 #endif
447 keypad (stdscr, TRUE);
448
449 top = current - PAGELEN / 2;
450 if (top < 0)
451 top = 0;
452
453 while (!done)
454 {
455 if (redraw == FULL)
456 {
457 move (0, 0);
458 clrtobot ();
459 standout ();
460 printw ("UrlView %s: (%d matches) Press Q or Ctrl-C to Quit!", VERSION, urlcount);
461 standend ();
462 redraw = INDEX;
463 }
464
465 if (redraw == INDEX)
466 {
467 for (i = top; i < urlcount && i < top + PAGELEN; i++)
468 {
469 mvaddstr (i - top + OFFSET, 0, " ");
470 snprintf (buf, sizeof (buf), "%4d ", i + 1);
471 addstr (buf);
472 addstr (url[i]);
473 clrtoeol ();
474 }
475 clrtobot ();
476 }
477 else if (redraw == MOTION)
478 mvaddstr (oldcurrent - top + OFFSET, 0, " ");
479
480 standout ();
481 mvaddstr (current - top + OFFSET, 0, "->");
482 standend ();
483
484 oldcurrent = current;
485
486 switch (i = getch ())
487 {
488 case 'q':
489 case 'x':
490 case 'h':
491 done = 1;
492 break;
493 case KEY_DOWN:
494 case 'j':
495 if (current < urlcount - 1)
496 {
497 current++;
498 if (current >= top + PAGELEN)
499 {
500 top++;
501 redraw = INDEX;
502 }
503 else
504 redraw = MOTION;
505 }
506 else
507 {
508 if(menu_wrapping)
509 current = 0;
510 if (current < top)
511 {
512 top = current - current % PAGELEN;
513 redraw = INDEX;
514 }
515 else
516 redraw = MOTION;
517 }
518 break;
519 case KEY_UP:
520 case 'k':
521 if (current)
522 {
523 current--;
524 if (current < top)
525 {
526 top--;
527 redraw = INDEX;
528 }
529 else
530 redraw = MOTION;
531 }
532 else
533 {
534 if(menu_wrapping)
535 current = urlcount - 1;
536 if (current > top + PAGELEN - 1)
537 {
538 top = current - current % PAGELEN;
539 redraw = INDEX;
540 }
541 else
542 redraw = MOTION;
543 }
544 break;
545 case KEY_HOME:
546 case '=':
547 if (top != 0)
548 {
549 top = 0;
550 redraw = INDEX;
551 }
552 else
553 redraw = MOTION;
554 current = 0;
555 break;
556 case KEY_END:
557 case '*':
558 case 'G':
559 current = urlcount - 1;
560 if (current >= top + PAGELEN)
561 {
562 top = current - PAGELEN + 1;
563 redraw = INDEX;
564 }
565 else
566 redraw = MOTION;
567 break;
568 case KEY_NPAGE:
569 case '\006':
570 if (top + PAGELEN < urlcount)
571 {
572 current = top = top + PAGELEN;
573 redraw = INDEX;
574 }
575 else
576 {
577 current = urlcount - 1;
578 redraw = INDEX;
579 }
580 break;
581 case KEY_PPAGE:
582 case '\002':
583 if (top)
584 {
585 top -= PAGELEN;
586 if (top < 0)
587 top = 0;
588 if (current >= top + PAGELEN)
589 {
590 current = top + PAGELEN - 1;
591 if (current < 0)
592 current = 0;
593 }
594 redraw = INDEX;
595 }
596 else
597 {
598 current = 0;
599 redraw = INDEX;
600 }
601 break;
602 case '\n':
603 case '\r':
604 case KEY_ENTER:
605 case ' ':
606 strncpy (buf, url[current], sizeof (buf));
607 buf[sizeof (buf) - 1] = 0;
608 mvaddstr (LINES - 1, 0, "URL: ");
609 if (mutt_enter_string ((unsigned char *)buf, sizeof (buf), LINES - 1, 5, 0) == 0 && buf[0])
610 {
611 char *part, *tmpbuf;
612
613 free (url[current]);
614 url[current] = strdup (buf);
615 endwin ();
616
617 tmpbuf = strdup(command);
618 part = strtok(tmpbuf, ":");
619 do {
620 quote (scratch, sizeof (scratch), url[current]);
621 if (strstr (part, "%s"))
622 snprintf (buf, sizeof (buf), part, scratch);
623 else
624 snprintf (buf, sizeof (buf), "%s %s", part, scratch);
625 printf ("Executing: %s...\n", buf);
626 fflush (stdout);
627 if (system (buf) == 0)
628 break;
629 } while
630 ((part = strtok(NULL, ":")) != NULL);
631 free(tmpbuf);
632 }
633 done = quitonlaunch;
634 move (LINES - 1, 0);
635 clrtoeol ();
636 break;
637 case '0':
638 case '1':
639 case '2':
640 case '3':
641 case '4':
642 case '5':
643 case '6':
644 case '7':
645 case '8':
646 case '9':
647 buf[0] = i; buf[1] = 0;
648 mvaddstr (LINES - 1, 0, "Jump to url: ");
649 if (mutt_enter_string ((unsigned char *)buf, sizeof (buf), LINES - 1, 13, 0) == 0 && buf[0])
650 {
651 i = atoi (buf);
652 if (i < 1 || i > urlcount)
653 {
654 mvaddstr (LINES - 1, 0, "No such url number!");
655 clrtoeol ();
656 }
657 else
658 {
659 move (LINES - 1, 0);
660 clrtoeol ();
661 current = i - 1;
662 redraw = MOTION;
663 if (current < top || current > top + PAGELEN - 1)
664 {
665 top = current - current % PAGELEN;
666 redraw = INDEX;
667 }
668 }
669 }
670 break;
671 case '\f':
672 clearok (stdscr, TRUE);
673 redraw = FULL;
674 break;
675 case '/':
676 mvaddstr (LINES - 1, 0, "Search forwards for string: ");
677 if (mutt_enter_string ((unsigned char *)search, sizeof (search), LINES - 1, 28, 0) == 0)
678 search_forward (search, urlcount, url, &redraw, ¤t, &top);
679 break;
680 case '?':
681 mvaddstr (LINES - 1, 0, "Search backwards for string: ");
682 if (mutt_enter_string ((unsigned char *)search, sizeof (search), LINES - 1, 29, 0) == 0)
683 search_backward (search, urlcount, url, &redraw, ¤t, &top);
684 break;
685 case 'n':
686 search_forward (search, urlcount, url, &redraw, ¤t, &top);
687 break;
688 case 'N':
689 search_backward (search, urlcount, url, &redraw, ¤t, &top);
690 break;
691 default:
692 break;
693 }
694 }
695
696 endwin ();
697 exit (0);
698 }
699