1 /*
2  * $LynxId: LYJump.c,v 1.45 2013/01/05 00:28:46 tom Exp $
3  */
4 #include <HTUtils.h>
5 #include <HTAlert.h>
6 #include <LYUtils.h>
7 #include <LYStrings.h>
8 #include <LYGlobalDefs.h>
9 #include <LYJump.h>
10 #include <LYKeymap.h>
11 #include <GridText.h>
12 
13 #include <LYLeaks.h>
14 
15 #ifdef _WINDOWS
16 #include <stdlib.h>		/* bsearch() */
17 #endif
18 
19 #ifdef VMS
20 #include <fab.h>
21 #endif /* VMS */
22 
23 struct JumpTable *JThead = NULL;
24 
25 static int LYCompare(const void *e1, const void *e2);
26 static unsigned LYRead_Jumpfile(struct JumpTable *jtp);
27 
LYJumpTable_free(void)28 void LYJumpTable_free(void)
29 {
30     struct JumpTable *cur = JThead;
31     struct JumpTable *next;
32 
33     while (cur) {
34 	next = cur->next;
35 	FREE(cur->msg);
36 	FREE(cur->file);
37 	FREE(cur->shortcut);
38 	if (cur->history) {
39 	    LYFreeStringList(cur->history);
40 	    cur->history = NULL;
41 	}
42 	FREE(cur->table);
43 	FREE(cur->mp);
44 	FREE(cur);
45 	cur = next;
46     }
47     JThead = NULL;
48     return;
49 }
50 
51 /*
52  * Utility for listing shortcuts, making any repeated
53  * shortcut the most current in the list. - FM
54  */
LYAddJumpShortcut(HTList * historyp,char * shortcut)55 void LYAddJumpShortcut(HTList *historyp, char *shortcut)
56 {
57     char *tmp = NULL;
58     char *old;
59     HTList *cur = historyp;
60 
61     if (!historyp || isEmpty(shortcut))
62 	return;
63 
64     StrAllocCopy(tmp, shortcut);
65 
66     while (NULL != (old = (char *) HTList_nextObject(cur))) {
67 	if (!strcmp(old, tmp)) {
68 	    HTList_removeObject(historyp, old);
69 	    FREE(old);
70 	    break;
71 	}
72     }
73     HTList_addObject(historyp, tmp);
74 
75     return;
76 }
77 
LYJumpInit(char * config)78 BOOL LYJumpInit(char *config)
79 {
80     struct JumpTable *jtp;
81     char *cp;
82 
83     /*
84      * Create a JumpTable structure.
85      */
86     jtp = typecalloc(struct JumpTable);
87 
88     if (jtp == NULL) {
89 	outofmem(__FILE__, "LYJumpInit");
90     }
91 
92     assert(jtp != NULL);
93 
94     /*
95      * config is JUMPFILE:path[:optional_key[:optional_prompt]]
96      *
97      * Skip JUMPFILE.
98      */
99     cp = strtok(config, ":\n");
100     if (!cp) {
101 	FREE(jtp);
102 	return FALSE;
103     }
104 
105     /*
106      * Get the path.
107      */
108     cp = strtok(NULL, ":\n");
109     if (!cp) {
110 	FREE(jtp);
111 	return FALSE;
112     }
113     StrAllocCopy(jtp->file, cp);
114 #ifdef LY_FIND_LEAKS
115     if (!JThead)
116 	atexit(LYJumpTable_free);
117 #endif /* LY_FIND_LEAKS */
118 
119     /*
120      * Get the key, if present.
121      */
122     cp = strtok(NULL, ":\n");
123 
124     /*
125      * If no key, check whether we are resetting the default jumps file.
126      */
127     if (!cp && JThead) {
128 	struct JumpTable *jtptmp = JThead;
129 
130 	jumpfile = jtp->file;
131 	FREE(jtp);
132 	while (jtptmp && jtptmp->key)
133 	    jtptmp = jtptmp->next;
134 	if (!jtptmp)
135 	    return FALSE;
136 	StrAllocCopy(jtptmp->file, jumpfile);
137 	StrAllocCopy(jtptmp->msg, jumpprompt);
138 	return TRUE;
139     }
140 
141     /*
142      * If a key is present and we have no default, create one,
143      * using the path from config, and the current jumpprompt.
144      */
145     if (cp && !JThead) {
146 	JThead = jtp;
147 	StrAllocCopy(JThead->msg, jumpprompt);
148 	if (!jumpfile)
149 	    StrAllocCopy(jumpfile, JThead->file);
150 	jtp = typecalloc(struct JumpTable);
151 
152 	if (jtp == NULL) {
153 	    outofmem(__FILE__, "LYJumpInit");
154 	}
155 
156 	assert(jtp != NULL);
157 
158 	StrAllocCopy(jtp->file, JThead->file);
159     }
160 
161     /*
162      * Complete the initialization of config.
163      */
164     if (cp) {
165 	jtp->key = remap(cp, "JUMP", FALSE);	/* key is present, (re)map it */
166 	cp = strtok(NULL, "\n");	/* get prompt, if present */
167 	if (non_empty(cp))
168 	    StrAllocCopy(jtp->msg, cp);		/* prompt is present, load it */
169 	else
170 	    cp = NULL;
171     }
172     if (!cp)			/* no prompt, use default */
173 	StrAllocCopy(jtp->msg, jumpprompt);
174     if (jtp->msg[strlen(jtp->msg) - 1] != ' ')	/* ensure a trailing space */
175 	StrAllocCat(jtp->msg, " ");
176     jtp->history = HTList_new();
177     jtp->next = JThead;
178     JThead = jtp;
179     return TRUE;
180 }
181 
LYJump(int key)182 char *LYJump(int key)
183 {
184     static bstring *buf = NULL;
185 
186     JumpDatum seeking;
187     JumpDatum *found;
188     char *bp, *cp;
189     struct JumpTable *jtp;
190     int ch;
191     RecallType recall;
192     int ShortcutTotal;
193     int ShortcutNum;
194     BOOLEAN FirstShortcutRecall = TRUE;
195 
196     if (!JThead)
197 	return NULL;
198     jtp = JThead;
199     while (jtp && jtp->key && jtp->key != key)
200 	jtp = jtp->next;
201     if (!jtp) {
202 	char *msg = 0;
203 
204 	HTSprintf0(&msg, KEY_NOT_MAPPED_TO_JUMP_FILE, key);
205 	HTAlert(msg);
206 	FREE(msg);
207 	return NULL;
208     }
209     if (!jtp->table)
210 	jtp->nel = LYRead_Jumpfile(jtp);
211     if (jtp->nel == 0)
212 	return NULL;
213 
214     if (!jump_buffer || isEmpty(jtp->shortcut)) {
215 	BStrCopy0(buf, "");
216     } else if (non_empty(jtp->shortcut)) {
217 	size_t len = (size_t) BStrLen(buf);
218 
219 	if (strlen(jtp->shortcut) > len) {
220 	    jtp->shortcut[len] = '\0';
221 	    BStrCopy0(buf, jtp->shortcut);
222 	}
223     }
224 
225     ShortcutTotal = (jtp->history ? HTList_count(jtp->history) : 0);
226     if (jump_buffer && !isBEmpty(buf)) {
227 	recall = ((ShortcutTotal > 1) ? RECALL_URL : NORECALL);
228 	ShortcutNum = 0;
229 	FirstShortcutRecall = FALSE;
230     } else {
231 	recall = ((ShortcutTotal >= 1) ? RECALL_URL : NORECALL);
232 	ShortcutNum = ShortcutTotal;
233 	FirstShortcutRecall = TRUE;
234     }
235 
236     statusline(jtp->msg);
237     if ((ch = LYgetBString(&buf, VISIBLE, 0, recall)) < 0) {
238 	/*
239 	 * User cancelled the Jump via ^G. - FM
240 	 */
241 	HTInfoMsg(CANCELLED);
242 	return NULL;
243     }
244 
245   check_recall:
246     bp = buf->str;
247     if (TOUPPER(key) == 'G' && StrNCmp(buf->str, "o ", 2) == 0)
248 	bp++;
249     bp = LYSkipBlanks(bp);
250     if (*bp == '\0' &&
251 	!(recall && (ch == UPARROW || ch == DNARROW))) {
252 	/*
253 	 * User cancelled the Jump via a zero-length string. - FM
254 	 */
255 	BStrCopy0(buf, "");
256 	StrAllocCopy(jtp->shortcut, buf->str);
257 	HTInfoMsg(CANCELLED);
258 	return NULL;
259     }
260 #ifdef PERMIT_GOTO_FROM_JUMP
261     if (strchr(bp, ':') || strchr(bp, '/')) {
262 	char *temp = NULL;
263 
264 	LYJumpFileURL = FALSE;
265 	if (no_goto) {
266 	    BStrCopy0(buf, "");
267 	    StrAllocCopy(jtp->shortcut, buf->str);
268 	    HTUserMsg(RANDOM_URL_DISALLOWED);
269 	    return NULL;
270 	}
271 	HTSprintf0(&temp, "Go %s", bp);
272 	BStrCopy0(buf, temp);
273 	FREE(temp);
274 	return (bp = buf->str);
275     }
276 #endif /* PERMIT_GOTO_FROM_JUMP */
277 
278     if (recall && ch == UPARROW) {
279 	if (FirstShortcutRecall) {
280 	    /*
281 	     * Use last Shortcut in the list. - FM
282 	     */
283 	    FirstShortcutRecall = FALSE;
284 	    ShortcutNum = 0;
285 	} else {
286 	    /*
287 	     * Go back to the previous Shortcut in the list. - FM
288 	     */
289 	    ShortcutNum++;
290 	}
291 	if (ShortcutNum >= ShortcutTotal)
292 	    /*
293 	     * Roll around to the last Shortcut in the list. - FM
294 	     */
295 	    ShortcutNum = 0;
296 	if ((cp = (char *) HTList_objectAt(jtp->history,
297 					   ShortcutNum)) != NULL) {
298 	    BStrCopy0(buf, cp);
299 	    if (jump_buffer && jtp->shortcut &&
300 		!strcmp(buf->str, jtp->shortcut)) {
301 		_statusline(EDIT_CURRENT_SHORTCUT);
302 	    } else if ((jump_buffer && ShortcutTotal == 2) ||
303 		       (!jump_buffer && ShortcutTotal == 1)) {
304 		_statusline(EDIT_THE_PREV_SHORTCUT);
305 	    } else {
306 		_statusline(EDIT_A_PREV_SHORTCUT);
307 	    }
308 	    if ((ch = LYgetBString(&buf, VISIBLE, 0, recall)) < 0) {
309 		/*
310 		 * User cancelled the jump via ^G.
311 		 */
312 		HTInfoMsg(CANCELLED);
313 		return NULL;
314 	    }
315 	    goto check_recall;
316 	}
317     } else if (recall && ch == DNARROW) {
318 	if (FirstShortcutRecall) {
319 	    /*
320 	     * Use the first Shortcut in the list. - FM
321 	     */
322 	    FirstShortcutRecall = FALSE;
323 	    ShortcutNum = ShortcutTotal - 1;
324 	} else {
325 	    /*
326 	     * Advance to the next Shortcut in the list. - FM
327 	     */
328 	    ShortcutNum--;
329 	}
330 	if (ShortcutNum < 0)
331 	    /*
332 	     * Roll around to the first Shortcut in the list. - FM
333 	     */
334 	    ShortcutNum = ShortcutTotal - 1;
335 	if ((cp = (char *) HTList_objectAt(jtp->history,
336 					   ShortcutNum)) != NULL) {
337 	    BStrCopy0(buf, cp);
338 	    if (jump_buffer && jtp->shortcut &&
339 		!strcmp(buf->str, jtp->shortcut)) {
340 		_statusline(EDIT_CURRENT_SHORTCUT);
341 	    } else if ((jump_buffer && ShortcutTotal == 2) ||
342 		       (!jump_buffer && ShortcutTotal == 1)) {
343 		_statusline(EDIT_THE_PREV_SHORTCUT);
344 	    } else {
345 		_statusline(EDIT_A_PREV_SHORTCUT);
346 	    }
347 	    if ((ch = LYgetBString(&buf, VISIBLE, 0, recall)) < 0) {
348 		/*
349 		 * User cancelled the jump via ^G.
350 		 */
351 		HTInfoMsg(CANCELLED);
352 		return NULL;
353 	    }
354 	    goto check_recall;
355 	}
356     }
357 
358     seeking.key = bp;
359     found = (JumpDatum *) bsearch((char *) &seeking, (char *) jtp->table,
360 				  (size_t) jtp->nel, sizeof(JumpDatum), LYCompare);
361     if (!found) {
362 	user_message("Unknown target '%s'", buf->str);
363 	LYSleepAlert();
364     }
365 
366     StrAllocCopy(jtp->shortcut, bp);
367     LYAddJumpShortcut(jtp->history, jtp->shortcut);
368     return found ? found->url : NULL;
369 }
370 
LYRead_Jumpfile(struct JumpTable * jtp)371 static unsigned LYRead_Jumpfile(struct JumpTable *jtp)
372 {
373     struct stat st;
374     unsigned int nel;
375     char *mp;
376     int fd;
377 
378 #ifdef VMS
379     int blocksize = 1024;
380     FILE *fp;
381     BOOL IsStream_LF = TRUE;
382 #endif /* VMS */
383     char *cp;
384     unsigned i;
385 
386     if (isEmpty(jtp->file))
387 	return 0;
388 
389     CTRACE((tfp, "Read Jumpfile %s\n", jtp->file));
390     if (stat(jtp->file, &st) < 0) {
391 	HTAlert(CANNOT_LOCATE_JUMP_FILE);
392 	return 0;
393     }
394 
395     /* allocate storage to read entire file */
396     if ((mp = typecallocn(char, (size_t) st.st_size + 1)) == NULL) {
397 	HTAlert(OUTOF_MEM_FOR_JUMP_FILE);
398 	return 0;
399     }
400 #ifdef VMS
401     if (st.st_fab_rfm != (char) FAB$C_STMLF) {
402 	/** It's a record-oriented file. **/
403 	IsStream_LF = FALSE;
404 	if ((fp = fopen(jtp->file, "r", "mbc=32")) == NULL) {
405 	    HTAlert(CANNOT_OPEN_JUMP_FILE);
406 	    FREE(mp);
407 	    return 0;
408 	}
409     } else if ((fd = open(jtp->file, O_RDONLY, "mbc=32")) < 0)
410 #else
411     if ((fd = open(jtp->file, O_RDONLY)) < 0)
412 #endif /* VMS */
413     {
414 	HTAlert(CANNOT_OPEN_JUMP_FILE);
415 	FREE(mp);
416 	return 0;
417     }
418 #ifdef VMS
419     if (IsStream_LF) {
420     /** Handle as a stream. **/
421 #endif /* VMS */
422 	if (read(fd, mp, (size_t) st.st_size) < st.st_size) {
423 	    HTAlert(ERROR_READING_JUMP_FILE);
424 	    FREE(mp);
425 	    close(fd);
426 	    return 0;
427 	}
428 	mp[st.st_size] = '\0';
429 	close(fd);
430 #ifdef VMS
431     } else {
432 	/** Handle as a series of records. **/
433 	if (fgets(mp, blocksize, fp) == NULL) {
434 	    HTAlert(ERROR_READING_JUMP_FILE);
435 	    FREE(mp);
436 	    close(fd);
437 	    return 0;
438 	} else {
439 	    while (fgets(mp + strlen(mp), blocksize, fp) != NULL) {
440 		;
441 	    }
442 	}
443 	LYCloseInput(fp);
444 	close(fd);
445     }
446 #endif /* VMS */
447 
448     /* quick scan for approximate number of entries */
449     nel = 0;
450     cp = mp;
451     while ((cp = strchr(cp, '\n')) != NULL) {
452 	nel++;
453 	cp++;
454     }
455 
456     jtp->table = (JumpDatum *) malloc((nel + 1) * sizeof(JumpDatum));
457     if (jtp->table == NULL) {
458 	HTAlert(OUTOF_MEM_FOR_JUMP_TABLE);
459 	FREE(mp);
460 	return 0;
461     }
462 
463     cp = jtp->mp = mp;
464     for (i = 0; i < nel;) {
465 	if (StrNCmp(cp, "<!--", 4) == 0 || StrNCmp(cp, "<dl>", 4) == 0) {
466 	    cp = strchr(cp, '\n');
467 	    if (cp == NULL)
468 		break;
469 	    cp++;
470 	    continue;
471 	}
472 	cp = LYstrstr(cp, "<dt>");
473 	if (cp == NULL)
474 	    break;
475 	cp += 4;
476 	jtp->table[i].key = cp;
477 	cp = LYstrstr(cp, "<dd>");
478 	if (cp == NULL)
479 	    break;
480 	*cp = '\0';
481 	cp += 4;
482 	cp = LYstrstr(cp, "href=\"");
483 	if (cp == NULL)
484 	    break;
485 	cp += 6;
486 	jtp->table[i].url = cp;
487 	cp = strchr(cp, '"');
488 	if (cp == NULL)
489 	    break;
490 	*cp = '\0';
491 	cp++;
492 	cp = strchr(cp, '\n');
493 	if (cp == NULL)
494 	    break;
495 	cp++;
496 	CTRACE((tfp, "Read jumpfile[%u] key='%s', url='%s'\n",
497 		i, jtp->table[i].key, jtp->table[i].url));
498 	i++;
499     }
500 
501     return i;
502 }
503 
LYCompare(const void * e1,const void * e2)504 static int LYCompare(const void *e1, const void *e2)
505 {
506     return strcasecomp(((const JumpDatum *) e1)->key,
507 		       ((const JumpDatum *) e2)->key);
508 }
509