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