1 /* retawq/main.c - main routine, user interaction
2 This file is part of retawq (<http://retawq.sourceforge.net/>), a network
3 client created by Arne Thomassen; retawq is basically released under certain
4 versions of the GNU General Public License and WITHOUT ANY WARRANTY.
5 Read the file COPYING for license details, README for program information.
6 Copyright (C) 2001-2005 Arne Thomassen <arne@arne-thomassen.de>
7 */
8
9 #include "stuff.h"
10 #include "init.h"
11 #include "resource.h"
12 #include "parser.h"
13
14 #if HAVE_ARPA_INET_H
15 #include <arpa/inet.h>
16 #endif
17
18 #if CONFIG_TG == TG_X
19 #include <X11/keysym.h>
20 #elif CONFIG_TG == TG_GTK
21 #include <gdk/gdkkeysyms.h>
22 #endif
23
24 declare_local_i18n_buffer
25
26
27 /** Strings */
28
29 static char strbuf[STRBUF_SIZE], strbuf2[STRBUF_SIZE], strbuf3[STRBUF_SIZE];
30 #if CONFIG_DEBUG
31 static char debugstrbuf[STRBUF_SIZE];
32 #endif
33
34 static const char strPercsDash[] = "%s - ", strDotSlash[] = "./",
35 strSlashSlash[] = "//";
36 #define strDot (strDoubleDot + 1) /* ugly :-) */
37
38 static const char strBracedNewWindow[] = N_("(new window)"),
39 strBack[] = N_("Back"), strForward[] = N_("Forward"),
40 strReload[] = N_("Reload"), strEnforcedReload[] = N_("Enforced Reload"),
41 strSearch[] = N_("Search"), strOpenInNewWindow[] = N_("Open in New Window"),
42 strSource[] = N_("source"), strSourceCode[] = N_("Source Code"),
43 strSave[] = N_("Save"), strEnableThisElement[] = N_("Enable this Element"),
44 strContext[] = N_("Context"), strShowElementInfo[] = N_("Show Element Info"),
45 strOpenLinkInNewWindow[] = N_("Open Link in New Window"),
46 strSelectionEmpty[] = N_("This selection list is empty!"),
47 strAddBookmark[] = N_("Add Bookmark"), strSaveAs[] = N_("Save As..."),
48 strSaveLinkAs[] = N_("Save Link As..."), strUriColonSpace[] = N_("URL: "),
49 strSubmitThisForm[] = N_("Submit this Form");
50 #if OPTION_COOKIES
51 static const char strCookies[] = N_("(cookies) ");
52 #endif
53 #if TGC_IS_GRAPHICS || CONFIG_CUSTOM_CONN
54 static const char strUcDownload[] = N_("Download");
55 #endif
56 #if CONFIG_SESSIONS || CONFIG_CONSOLE
57 static const char strDone[] = N_("Done");
58 #endif
59
60 static const char* const strAek[MAX_AEK + 1] =
61 { strUnknown, strLink, N_("text field"), N_("password field"),
62 N_(strCheckbox), N_("radio button"), N_("submit button"), N_("reset button"),
63 N_("file field"), N_("text-area field"), N_("selection"), N_(strButton),
64 N_(strImage), N_("hidden field")
65 };
66
67 static const char strUcClose[] = N_("Close"), strUcQuit[] = N_("Quit");
68
69 #if CONFIG_KEYMAPS
70 static const char strMouseFlip[] = "mouse-flip", strMouseOff[] = "mouse-off",
71 strMouseOn[] = "mouse-on";
72 #endif
73
74 #if CONFIG_FTP
75 static const char strAnonymous[] = "anonymous";
76 #endif
77
78
79 /** Keys'n'curses */
80
81 #if TGC_IS_GRAPHICS
82
83 #if CONFIG_TG == TG_X
84
85 #define KEY_ESCAPE (XK_Escape)
86 #define KEY_CANCEL (XK_Cancel)
87 #define KEY_LEFT (XK_Left)
88 #define KEY_RIGHT (XK_Right)
89 #define KEY_END (XK_End)
90 #define KEY_NPAGE (XK_Page_Down)
91 #define KEY_PPAGE (XK_Page_Up)
92 #define KEY_DOWN (XK_Down)
93 #define KEY_UP (XK_Up)
94 #define KEY_HOME (XK_Home)
95 #define KEY_ENTER (XK_Return)
96 #define KEY_BACKSPACE (XK_BackSpace)
97 #define KEY_DC (XK_Delete)
98 #define KEY_IC (XK_Insert)
99
100 #elif CONFIG_TG == TG_GTK
101
102 FAIL!
103 #define KEY_ESCAPE (GDK_Escape)
104 #define KEY_CANCEL (GDK_Cancel)
105 #define KEY_LEFT (GDK_Left)
106 #define KEY_RIGHT (GDK_Right)
107 #define KEY_END (GDK_End)
108 #define KEY_NPAGE (GDK_Page_Down)
109 #define KEY_PPAGE (GDK_Page_Up)
110 #define KEY_DOWN (GDK_Down)
111 #define KEY_UP (GDK_Up)
112 #define KEY_HOME (GDK_Home)
113 #define KEY_ENTER (GDK_Return)
114 #define KEY_BACKSPACE (GDK_BackSpace)
115 #define KEY_DC (GDK_Delete)
116 #define KEY_IC (GDK_Insert)
117
118 #endif
119
120 /* stubs'n'dummies - for now... */
121 #define COLS (80)
122 #define LINES (25)
123 #define A_NORMAL (0)
124 #define A_BOLD (0)
125 #define A_UNDERLINE (0)
126 #define A_REVERSE (0)
127 #define OK (0)
128 #define ERR (-1)
129 typedef unsigned long chtype;
130 typedef chtype attr_t;
131 typedef struct { short _curx, _cury; } tTextualWindowContents;
move(int y __cunused,int x __cunused)132 static my_inline int move(int y __cunused, int x __cunused) { return(OK); }
clrtoeol(void)133 static my_inline int clrtoeol(void) { return(OK); }
addnstr(const char * s __cunused,int n __cunused)134 static my_inline int addnstr(const char* s __cunused, int n __cunused)
135 { return(OK); }
mvaddnstr(int y,int x,const char * s,int n)136 static my_inline int mvaddnstr(int y, int x, const char* s, int n)
137 { move(y, x); addnstr(s, n); return(OK); }
addch(const chtype c __cunused)138 static my_inline int addch(const chtype c __cunused) { return(OK); }
mvaddch(int y,int x,const chtype c)139 static my_inline int mvaddch(int y, int x, const chtype c)
140 { move(y, x); addch(c); return(OK); }
addchnstr(const chtype * s __cunused,int n __cunused)141 static my_inline int addchnstr(const chtype* s __cunused, int n __cunused)
142 { return(OK); }
COLOR_PAIR(int x __cunused)143 static my_inline int COLOR_PAIR(int x __cunused) { return(0); }
color_set(short x __cunused,void * d __cunused)144 static my_inline int color_set(short x __cunused, void* d __cunused)
145 { return(OK); }
146 /* static my_inline int attron(attr_t a __cunused) { return(OK); }
147 static my_inline int attroff(attr_t a __cunused) { return(OK); }
148 static one_caller int refresh(void) { return(OK); } */
inch(void)149 static my_inline chtype inch(void) { return(0); }
150
151 #elif TGC_IS_CURSES
152
153 #if CONFIG_TG != TG_XCURSES
154 #undef KEY_ESCAPE
155 #define KEY_ESCAPE '\033'
156 #endif
157
158 #ifdef ACS_HLINE
159 #define __MY_HLINE (ACS_HLINE)
160 #else
161 #define __MY_HLINE ('-')
162 #endif
163
164 #ifdef ACS_VLINE
165 #define __MY_VLINE (ACS_VLINE)
166 #else
167 #define __MY_VLINE ('|')
168 #endif
169
170 #ifdef ACS_ULCORNER
171 #define __MY_UL (ACS_ULCORNER)
172 #else
173 #define __MY_UL ('+')
174 #endif
175
176 #ifdef ACS_URCORNER
177 #define __MY_UR (ACS_URCORNER)
178 #else
179 #define __MY_UR ('+')
180 #endif
181
182 #ifdef ACS_LLCORNER
183 #define __MY_LL (ACS_LLCORNER)
184 #else
185 #define __MY_LL ('+')
186 #endif
187
188 #ifdef ACS_LRCORNER
189 #define __MY_LR (ACS_LRCORNER)
190 #else
191 #define __MY_LR ('+')
192 #endif
193
194 #ifdef ACS_LTEE
195 #define __MY_LTEE (ACS_LTEE)
196 #else
197 #define __MY_LTEE ('+')
198 #endif
199
200 #ifdef ACS_RTEE
201 #define __MY_RTEE (ACS_RTEE)
202 #else
203 #define __MY_RTEE ('+')
204 #endif
205
206 #define VMIDDLE (MAX(((LINES - 2) / 2), 1))
207
208 static tBoolean __must_reset_cursor = falsE;
must_reset_cursor(void)209 static __my_inline void must_reset_cursor(void) { __must_reset_cursor = truE; }
210
211 #endif
212
213 #ifndef KEY_TAB
214 #define KEY_TAB '\t'
215 #endif
216
217 #if OPTION_CED == 0
218 #define is_bad_uchar(c) ( ((c) < 32) || ((c) >= 127) )
219 #else
220 #define is_bad_uchar(c) ( ((c) < 32) || ((c) == 127) )
221 #endif
222
223
224 /** Keymaps */
225
226 /* begin-autogenerated */
227 my_enum1 enum
228 { pccUnknown = 0, pccDocumentBottom = 1, pccDocumentEnforceHtml = 2,
229 pccDocumentEnforceSource = 3, pccDocumentInfo = 4, pccDocumentReload = 5,
230 pccDocumentReloadEnforced = 6, pccDocumentSave = 7, pccDocumentSearch = 8,
231 pccDocumentSearchBackward = 9, pccDocumentSearchNext = 10,
232 pccDocumentSearchPrevious = 11, pccDocumentTop = 12, pccDownload = 13,
233 pccDownloadFromElement = 14, pccDump = 15, pccElementEnable = 16,
234 pccElementInfo = 17, pccElementNext = 18, pccElementOpen = 19,
235 pccElementOpenSplit = 20, pccElementPrevious = 21, pccExecextShell = 22,
236 pccExecextShellFlip = 23, pccFormReset = 24, pccFormSubmit = 25,
237 pccGoBookmarks = 26, pccGoHome = 27, pccGoSearch = 28, pccGoUri = 29,
238 pccGoUriPreset = 30, pccJump = 31, pccJumpPreset = 32, pccLineDown = 33,
239 pccLineUp = 34, pccLocalFileDirOpen = 35, pccMenuContextual = 36,
240 pccMenuUriHistory = 37, pccMenuWindowlist = 38, pccMouseFlip = 39,
241 pccMouseOff = 40, pccMouseOn = 41, pccPageDown = 42, pccPageUp = 43,
242 pccQuit = 44, pccScreenSplit = 45, pccScreenSwitch = 46,
243 pccScreenUnsplit = 47, pccScrollBarsFlip = 48, pccSessionResume = 49,
244 pccSessionSave = 50, pccStop = 51, pccViewBack = 52, pccViewForward = 53,
245 pccWindowClose = 54, pccWindowNew = 55, pccWindowNewFromDocument = 56,
246 pccWindowNewFromElement = 57, pccWindowNext = 58, pccWindowPrevious = 59
247 } my_enum2(unsigned char) tProgramCommandCode;
248
249 typedef struct
250 { tKey key;
251 tProgramCommandCode pcc;
252 } tKeymapCommandEntry;
253
254 static const tKeymapCommandEntry keymap_command_defaultkeys[] =
255 { { '!', pccExecextShell },
256 { '&', pccExecextShellFlip },
257 { '.', pccStop },
258 { '/', pccDocumentSearch },
259 { '1', pccScreenUnsplit },
260 { '2', pccScreenSplit },
261 { '?', pccDocumentSearchBackward },
262 { 'C', pccWindowClose },
263 { 'D', pccDownloadFromElement },
264 { 'E', pccElementEnable },
265 { 'G', pccGoUriPreset },
266 { 'H', pccDocumentEnforceHtml },
267 { 'I', pccDocumentInfo },
268 { 'J', pccJumpPreset },
269 { 'L', pccLineUp },
270 { 'M', pccSessionResume },
271 { 'N', pccWindowNewFromDocument },
272 { 'O', pccWindowNewFromElement },
273 { 'Q', pccQuit },
274 { 'R', pccDocumentReloadEnforced },
275 { 'S', pccSessionSave },
276 { 'W', pccWindowPrevious },
277 { 'Y', pccMouseFlip },
278 { '\\', pccDocumentEnforceSource },
279 { 'b', pccGoBookmarks },
280 { 4, pccDownload },
281 { 15, pccElementOpenSplit },
282 { 23, pccMenuWindowlist },
283 { KEY_DOWN, pccElementNext },
284 { KEY_LEFT, pccViewBack },
285 { KEY_RIGHT, pccViewForward },
286 { KEY_UP, pccElementPrevious },
287 { 'd', pccDump },
288 { KEY_DC, pccLineDown },
289 { 'e', pccGoSearch },
290 { KEY_END, pccDocumentBottom },
291 { KEY_ENTER, pccElementOpen },
292 { 'g', pccGoUri },
293 { 'h', pccGoHome },
294 { KEY_HOME, pccDocumentTop },
295 { 'i', pccElementInfo },
296 { KEY_IC, pccLineUp },
297 { 'j', pccJump },
298 { 'l', pccLineDown },
299 { 'm', pccMenuContextual },
300 { 'n', pccWindowNew },
301 { 'o', pccElementOpen },
302 { KEY_NPAGE, pccPageDown },
303 { KEY_PPAGE, pccPageUp },
304 { 'r', pccDocumentReload },
305 { 's', pccDocumentSave },
306 { ' ', pccPageDown },
307 { KEY_TAB, pccScreenSwitch },
308 { 'u', pccMenuUriHistory },
309 { 'w', pccWindowNext }
310 };
311
312 #if CONFIG_KEYMAPS
313
314 static const struct
315 { const char* str; /* (sorted in strcmp() order) */
316 tProgramCommandCode pcc; /* REMOVEME? */
317 } keymap_command_str2pcc[] =
318 { { "document-bottom", pccDocumentBottom },
319 { "document-enforce-html", pccDocumentEnforceHtml },
320 { "document-enforce-source", pccDocumentEnforceSource },
321 { "document-info", pccDocumentInfo },
322 { "document-reload", pccDocumentReload },
323 { "document-reload-enforced", pccDocumentReloadEnforced },
324 { "document-save", pccDocumentSave },
325 { "document-search", pccDocumentSearch },
326 { "document-search-backward", pccDocumentSearchBackward },
327 { "document-search-next", pccDocumentSearchNext },
328 { "document-search-previous", pccDocumentSearchPrevious },
329 { "document-top", pccDocumentTop },
330 { "download", pccDownload },
331 { "download-from-element", pccDownloadFromElement },
332 { "dump", pccDump },
333 { "element-enable", pccElementEnable },
334 { "element-info", pccElementInfo },
335 { "element-next", pccElementNext },
336 { "element-open", pccElementOpen },
337 { "element-open-split", pccElementOpenSplit },
338 { "element-previous", pccElementPrevious },
339 { "execext-shell", pccExecextShell },
340 { "execext-shell-flip", pccExecextShellFlip },
341 { "form-reset", pccFormReset },
342 { "form-submit", pccFormSubmit },
343 { "go-bookmarks", pccGoBookmarks },
344 { "go-home", pccGoHome },
345 { "go-search", pccGoSearch },
346 { "go-url", pccGoUri },
347 { "go-url-preset", pccGoUriPreset },
348 { "jump", pccJump },
349 { "jump-preset", pccJumpPreset },
350 { "line-down", pccLineDown },
351 { "line-up", pccLineUp },
352 { "local-file-dir-open", pccLocalFileDirOpen },
353 { "menu-contextual", pccMenuContextual },
354 { "menu-url-history", pccMenuUriHistory },
355 { "menu-windowlist", pccMenuWindowlist },
356 { strMouseFlip, pccMouseFlip },
357 { strMouseOff, pccMouseOff },
358 { strMouseOn, pccMouseOn },
359 { "page-down", pccPageDown },
360 { "page-up", pccPageUp },
361 { strQuit, pccQuit },
362 { "screen-split", pccScreenSplit },
363 { "screen-switch", pccScreenSwitch },
364 { "screen-unsplit", pccScreenUnsplit },
365 { "scroll-bars-flip", pccScrollBarsFlip },
366 { "session-resume", pccSessionResume },
367 { "session-save", pccSessionSave },
368 { "stop", pccStop },
369 { "view-back", pccViewBack },
370 { "view-forward", pccViewForward },
371 { "window-close", pccWindowClose },
372 { "window-new", pccWindowNew },
373 { "window-new-from-document", pccWindowNewFromDocument },
374 { "window-new-from-element", pccWindowNewFromElement },
375 { "window-next", pccWindowNext },
376 { "window-previous", pccWindowPrevious }
377 };
378
379 #endif
380
381 my_enum1 enum
382 { liacUnknown = 0, liacAreaSwitch = 1, liacCancel = 2, liacMouseFlip = 3,
383 liacMouseOff = 4, liacMouseOn = 5, liacPass2user = 6, liacToEnd = 7,
384 liacToLeft = 8, liacToRight = 9, liacToStart = 10
385 } my_enum2(unsigned char) tLineInputActionCode;
386
387 typedef struct
388 { tKey key;
389 tLineInputActionCode liac;
390 } tKeymapLineinputEntry;
391
392 static const tKeymapLineinputEntry keymap_lineinput_defaultkeys[] =
393 { { 1, liacAreaSwitch },
394 { 21, liacPass2user },
395 { KEY_DOWN, liacToEnd },
396 { KEY_LEFT, liacToLeft },
397 { KEY_RIGHT, liacToRight },
398 { KEY_UP, liacToStart },
399 { KEY_END, liacToEnd },
400 { KEY_CANCEL, liacCancel },
401 { KEY_HOME, liacToStart },
402 { KEY_NPAGE, liacToEnd },
403 { KEY_PPAGE, liacToStart }
404 };
405
406 #if CONFIG_KEYMAPS
407
408 static const struct
409 { const char* str; /* (sorted in strcmp() order) */
410 tLineInputActionCode liac; /* REMOVEME? */
411 } keymap_lineinput_str2liac[] =
412 { { "area-switch", liacAreaSwitch },
413 { "cancel", liacCancel },
414 { strMouseFlip, liacMouseFlip },
415 { strMouseOff, liacMouseOff },
416 { strMouseOn, liacMouseOn },
417 { "pass2user", liacPass2user },
418 { "to-end", liacToEnd },
419 { "to-left", liacToLeft },
420 { "to-right", liacToRight },
421 { "to-start", liacToStart }
422 };
423
424 #endif
425 /* end-autogenerated */
426
427 static const_after_init tKeymapCommandEntry* keymap_command_keys = NULL;
428 static const_after_init size_t keymap_command_keys_num = 0,
429 keymap_command_keys_maxnum = 0;
430
keymap_command_sorter(const void * _a,const void * _b)431 static int __init keymap_command_sorter(const void* _a, const void* _b)
432 { const tKeymapCommandEntry *a = (const tKeymapCommandEntry*) _a,
433 *b = (const tKeymapCommandEntry*) _b;
434 tKey ak = a->key, bk = b->key;
435 return(my_numcmp(ak, bk));
436 }
437
keymap_command_key_do_register(tKey key,tProgramCommandCode pcc)438 static tBoolean __init keymap_command_key_do_register(tKey key,
439 tProgramCommandCode pcc)
440 /* returns whether it worked */
441 { size_t count;
442 for (count = 0; count < keymap_command_keys_num; count++) /* IMPROVEME? */
443 { if (keymap_command_keys[count].key == key) return(falsE); } /* redefined */
444
445 if (keymap_command_keys_num >= keymap_command_keys_maxnum)
446 { keymap_command_keys_maxnum += 32;
447 keymap_command_keys = (tKeymapCommandEntry*)
448 memory_reallocate(keymap_command_keys, keymap_command_keys_maxnum *
449 sizeof(tKeymapCommandEntry), mapKeymap);
450 }
451 keymap_command_keys[keymap_command_keys_num].key = key;
452 keymap_command_keys[keymap_command_keys_num].pcc = pcc;
453 keymap_command_keys_num++;
454 return(truE);
455 }
456
457 static const_after_init tKeymapLineinputEntry* keymap_lineinput_keys = NULL;
458 static const_after_init size_t keymap_lineinput_keys_num = 0,
459 keymap_lineinput_keys_maxnum = 0;
460
keymap_lineinput_sorter(const void * _a,const void * _b)461 static int __init keymap_lineinput_sorter(const void* _a, const void* _b)
462 { const tKeymapLineinputEntry *a = (const tKeymapLineinputEntry*) _a,
463 *b = (const tKeymapLineinputEntry*) _b;
464 tKey ak = a->key, bk = b->key;
465 return(my_numcmp(ak, bk));
466 }
467
keymap_lineinput_key_do_register(tKey key,tLineInputActionCode liac)468 static tBoolean __init keymap_lineinput_key_do_register(tKey key,
469 tLineInputActionCode liac)
470 /* returns whether it worked */
471 { size_t count;
472 for (count = 0; count < keymap_lineinput_keys_num; count++) /* IMPROVEME? */
473 { if (keymap_lineinput_keys[count].key == key) return(falsE); } /*redefined*/
474
475 if (keymap_lineinput_keys_num >= keymap_lineinput_keys_maxnum)
476 { keymap_lineinput_keys_maxnum += 16;
477 keymap_lineinput_keys = (tKeymapLineinputEntry*)
478 memory_reallocate(keymap_lineinput_keys, keymap_lineinput_keys_maxnum *
479 sizeof(tKeymapLineinputEntry), mapKeymap);
480 }
481 keymap_lineinput_keys[keymap_lineinput_keys_num].key = key;
482 keymap_lineinput_keys[keymap_lineinput_keys_num].liac = liac;
483 keymap_lineinput_keys_num++;
484 return(truE);
485 }
486
487 #if CONFIG_KEYMAPS
488
keystr2key(const char * keystr)489 static tKey __init keystr2key(const char* keystr)
490 { tKey key = '\0';
491 unsigned char ch = *keystr;
492 if ( (ch != '\0') && (keystr[1] == '\0') ) key = ch; /* most likely case? */
493 else if (!strcmp(keystr, "delete")) key = KEY_DC;
494 else if (!strcmp(keystr, "end")) key = KEY_END;
495 /* else if (!strcmp(keystr, "enter")) key = KEY_ENTER; */
496 else if (!strcmp(keystr, "insert")) key = KEY_IC;
497 else if (!strcmp(keystr, "home")) key = KEY_HOME;
498 else if (!strcmp(keystr, "page-down")) key = KEY_NPAGE;
499 else if (!strcmp(keystr, "page-up")) key = KEY_PPAGE;
500 else if (!strcmp(keystr, "escape")) key = KEY_CANCEL;
501 else if (!strcmp(keystr, "space")) key = ' ';
502 else if (!strcmp(keystr, "tab")) key = '\t';
503 else if (!strncmp(keystr, "ctrl-", 5))
504 { char cc = keystr[5]; /* "control character" */
505 if ( (my_islower(cc)) && (keystr[6] == '\0') ) key = cc - 'a' + 1;
506 }
507 else if (!strncmp(keystr, "cursor-", 7))
508 { const char* tmp = keystr + 7;
509 if (!strcmp(tmp, "down")) key = KEY_DOWN;
510 else if (!strcmp(tmp, "left")) key = KEY_LEFT;
511 else if (!strcmp(tmp, "right")) key = KEY_RIGHT;
512 else if (!strcmp(tmp, "up")) key = KEY_UP;
513 }
514 #ifdef KEY_F
515 else if (!strncmp(keystr, "fn-", 3))
516 { const char* tmp = keystr + 3;
517 if (my_isdigit(*tmp))
518 { int num;
519 my_atoi(tmp, &num, &tmp, 99);
520 if ( (num >= 0) && (num <= 63) && (*tmp == '\0') ) key = KEY_F(num);
521 }
522 }
523 #endif
524 return(key);
525 }
526
keymap_cmdstr_lookup(const char * str)527 static one_caller tMbsIndex keymap_cmdstr_lookup(const char* str)
528 { my_binary_search(0, ARRAY_ELEMNUM(keymap_command_str2pcc) - 1,
529 strcmp(str, keymap_command_str2pcc[idx].str), return(idx))
530 }
531
keymap_command_key_register(const char * keystr,const char * cmdstr)532 unsigned char __init keymap_command_key_register(const char* keystr,
533 const char* cmdstr)
534 /* return value: 0=fine, 1=bad key identifier; 2=repeatedly defined key; 3=bad
535 command; IMPROVEME: binary search for <keystr>? */
536 { tKey key = keystr2key(keystr);
537 tMbsIndex idx;
538 tProgramCommandCode pcc;
539
540 if (key == '\0') return(1);
541
542 /* interpret the command */
543 idx = keymap_cmdstr_lookup(cmdstr);
544 if (idx < 0) return(3);
545 pcc = keymap_command_str2pcc[idx].pcc;
546
547 /* register */
548 if (!keymap_command_key_do_register(key, pcc)) return(2);
549 return(0);
550 }
551
keymap_listr_lookup(const char * str)552 static one_caller tMbsIndex keymap_listr_lookup(const char* str)
553 { my_binary_search(0, ARRAY_ELEMNUM(keymap_lineinput_str2liac) - 1,
554 strcmp(str, keymap_lineinput_str2liac[idx].str), return(idx))
555 }
556
keymap_lineinput_key_register(const char * keystr,const char * listr)557 unsigned char __init keymap_lineinput_key_register(const char* keystr,
558 const char* listr)
559 /* return value: 0=fine, 1=bad key identifier; 2=repeatedly defined key; 3=bad
560 command; IMPROVEME: binary search for <keystr>? */
561 { tKey key = keystr2key(keystr);
562 tMbsIndex idx;
563 tLineInputActionCode liac;
564
565 if (key == '\0') return(1);
566
567 /* interpret the action */
568 idx = keymap_listr_lookup(listr);
569 if (idx < 0) return(3);
570 liac = keymap_lineinput_str2liac[idx].liac;
571
572 /* register */
573 if (!keymap_lineinput_key_do_register(key, liac)) return(2);
574 return(0);
575 }
576
577 #endif /* #if CONFIG_KEYMAPS */
578
579 #if TGC_IS_GRAPHICS
580
581 #define is_khm_command (truE) /* only one key-handling mode available... */
582
583 #else
584
585 enum
586 { khmCommand = 0, khmLineInput = 1
587 #if CONFIG_MENUS
588 , khmMenu = 2
589 #endif
590 };
591 typedef unsigned char tKeyHandlingMode;
592 static tKeyHandlingMode key_handling_mode = khmCommand;
593 #define is_khm_command (key_handling_mode == khmCommand)
594
595 #define is_bottom_occupied (key_handling_mode == khmLineInput)
596
597 #endif
598
599
600 /** Helper functions */
601
my_getcwd(void)602 static const char* my_getcwd(void)
603 {
604 #if !HAVE_GETCWD
605 return(strSlash); /* CHECKME! */
606 #else
607 static const char* cwd = NULL;
608 if (cwd == NULL) /* not yet calculated */
609 { char buf[2060]; /* 1<<11 + 42*epsilon */
610 if ( (getcwd(buf, sizeof(buf) - 5) != buf) || (*buf == '\0') )
611 cwd = strSlash;
612 else
613 { const size_t len = strlen(buf);
614 if (buf[len - 1] != chDirsep) { buf[len] = chDirsep; buf[len+1] = '\0'; }
615 cwd = my_strdup(buf);
616 }
617 }
618 return(cwd);
619 #endif
620 }
621
622 #if TGC_IS_GRAPHICS
623 static my_inline __sallocator char* __callocator
my_strdup_ucfirst(const char * str)624 my_strdup_ucfirst(const char* str)
625 /* duplicates a string, changing the first character to uppercase */
626 { char *retval = my_strdup(str), ch = *retval;
627 *retval = my_toupper(ch);
628 return(retval);
629 }
630 #endif
631
632 #if OPTION_TLS
tls_errtext(const tResource * resource,char * dest)633 static tBoolean tls_errtext(const tResource* resource, /*@out@*/ char* dest)
634 { tBoolean retval;
635 tTlsError te = resource->tls_error;
636 if (!is_tls_error_expressive(te)) retval = falsE;
637 else
638 { sprint_safe(dest, _("TLS error - %s"), _(strTlsError[te])); retval=truE; }
639 return(retval);
640 }
641 #endif
642
643 #if 1 /* !CONFIG_JAVASCRIPT */
644 #define javascript_handle_event(kind, _ae) do { } while (0)
645 #else
javascript_handle_event(tJavascriptEventKind kind,tActiveElementNumber _ae)646 static void javascript_handle_event(tJavascriptEventKind kind,
647 tActiveElementNumber _ae)
648 { tWindowView* view = current_window->current_view;
649 const tJavascriptEventHandler* eh = view->request->resource->aebase[_ae].eh;
650 while (eh != NULL)
651 { if (eh->kind == kind)
652 { const tJavascriptCode* code = eh->code;
653 if (code != NULL) javascript_execute(view, _ae, code);
654 break;
655 }
656 eh = eh->next;
657 }
658 }
659 #endif
660
661
662 /** Remaining work mechanism */
663
664 struct tRemainingWork;
665 typedef tBoolean (*tRemainingWorkCallback)(const struct tRemainingWork*);
666
667 typedef struct tRemainingWork
668 { struct tRemainingWork* next;
669 tRemainingWorkCallback callback;
670 void *data1, *data2, *data3; /* private data for the callback handler */
671 } tRemainingWork;
672
673 static tRemainingWork *rw_head = NULL, *rw_tail = NULL;
674
remaining_work_store(tRemainingWork * rw)675 static void remaining_work_store(tRemainingWork* rw)
676 { if (rw_head == NULL) rw_head = rw;
677 if (rw_tail != NULL) rw_tail->next = rw;
678 rw_tail = rw;
679 }
680
remaining_work_create(tRemainingWorkCallback callback)681 static tRemainingWork* remaining_work_create(tRemainingWorkCallback callback)
682 { tRemainingWork* retval = memory_allocate(sizeof(tRemainingWork), mapOther);
683 retval->callback = callback; remaining_work_store(retval); return(retval);
684 }
685
remaining_work_do(void)686 static void remaining_work_do(void)
687 { tRemainingWork* rw = rw_head;
688 if (rw == NULL) return; /* the most likely case */
689 rw_head = rw_tail = NULL; /* detach */
690 while (rw != NULL)
691 { tRemainingWork* next = rw->next;
692 if ((rw->callback)(rw)) remaining_work_store(rw); /* keep it for retry */
693 else memory_deallocate(rw);
694 rw = next;
695 }
696 }
697
698
699 /** Generic graphics stuff */
700
701 #if TGC_IS_GRAPHICS
702
703 static const char strCancel[] = N_("Cancel");
704 static const_after_init char *istrYesUc, *istrNoUc;
705
706 #if CONFIG_TG == TG_X
707
708 /* some minor, not-yet-complete abstraction for the future... */
709 typedef void tGraphicsWidget;
710 typedef void tGraphicsWindow;
711 typedef void tGraphicsLowlevelWindow;
712 typedef GC tGraphicsContext;
713 typedef XEvent tGraphicsEvent;
714 typedef XFontStruct tGraphicsFont;
715
716 Display* xws_display = NULL;
717
718 #else
719
720 /* some minor, not-yet-complete abstraction for the future... */
721 typedef GtkWidget tGraphicsWidget;
722 typedef GtkWindow tGraphicsWindow;
723 typedef GdkWindow tGraphicsLowlevelWindow;
724 typedef GdkGC tGraphicsContext;
725 typedef GdkEvent tGraphicsEvent;
726 typedef GdkFont tGraphicsFont;
727
728 static const char strGtkDestroy[] = "destroy", strGtkDelete[] = "delete_event",
729 strGtkConfigure[] = "configure_event", strGtkClicked[] = "clicked",
730 strGtkButton[] = "button_press_event", strGtkActivate[] = "activate",
731 strGtkKey[] = "key_press_event";
732
733 #endif
734
735 static const tGraphicsFont* default_font;
736
737 #endif /* #if TGC_IS_GRAPHICS */
738
739
740 /** Line input I */
741
742 my_enum1 enum
743 { liekFail = 0, liekCancel = 1, liekKey = 2
744 } my_enum2(unsigned char) tLineInputEventKind;
745
746 typedef void (*tLineInputCallback)(void*, tLineInputEventKind, tKey key);
747
748 my_enum1 enum
749 { liafNone = 0, liafFocusable = 0x01, liafEditable = 0x02,
750 liafDisguising = 0x04, liafAllowEmptyText = 0x08
751 } my_enum2(unsigned char) tLineInputAreaFlags;
752
753 typedef struct
754 { char* text;
755 short len, maxlen, pos, first; /* CHECKME: size_t? */
756 short row, colmin, colcurr, colmax, usable;
757 tLineInputAreaFlags flags;
758 } tLineInputArea;
759 typedef unsigned char tLineInputAreaIndex;
760
761 my_enum1 enum
762 { prrfNone = 0, prrfRedrawAll = 0x01, prrfRedrawOne = 0x02, prrfSource = 0x04,
763 prrfHtml = 0x08, prrfReload = 0x10, prrfEnforcedReload = 0x20,
764 prrfIsHttpRedirection = 0x40, prrfUseSfbuf = 0x80, prrfPost = 0x100,
765 prrfWantUriAnchor = 0x200, prrfUpsfp4 = 0x400
766 } my_enum2(unsigned short) tPrrFlags; /* for prepare_resource_request() */
767 #define prrfIsRedirection (prrfIsHttpRedirection)
768
769 static tSinkingData sinking_data;
770
sinking_data_reset(void)771 static my_inline void sinking_data_reset(void)
772 { my_memclr_var(sinking_data);
773 }
774
sinking_data_shift(tSinkingData ** _s)775 static void sinking_data_shift(/*@out@*/ tSinkingData** _s)
776 { tSinkingData* s = *_s = __memory_allocate(sizeof(tSinkingData),
777 mapSinkingData);
778 my_memcpy(s, &sinking_data, sizeof(tSinkingData));
779 sinking_data_reset();
780 }
781
sinking_data_mcleanup(void)782 static my_inline void sinking_data_mcleanup(void)
783 { sinking_data_cleanup(&sinking_data); sinking_data_reset();
784 }
785
786 static struct
787 { tLineInputArea area[2];
788 #if TGC_IS_GRAPHICS
789 tWindow* window;
790 GtkEntry* entry;
791 #endif
792 tLineInputCallback callback;
793 void* callback_data;
794 tPrrFlags prrf; /* rarely needed */
795 tLineInputAreaIndex curr, num_areas;
796 } lid; /* aka "line input data" */
797
798 #define lid_area(what) (lid.area[lid.curr].what)
799
800 my_enum1 enum
801 { eligGoToUri = 0, eligSaveAs = 1
802 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
803 , eligDownloadUri = 2, eligDownloadFilename = 3
804 #endif
805 #if CONFIG_EXTRA & EXTRA_DUMP
806 , eligDumpFilename = 4
807 #endif
808 , eligDocumentSearch = 5, eligDocumentSearchBackward = 6
809 #if CONFIG_USER_QUERY
810 , eligUsername = 7, eligPassword = 8
811 #endif
812 #if !TGC_IS_GRAPHICS
813 , eligFormText = 9, eligFormPassword = 10, eligFormFilename = 11
814 #endif
815 #if CONFIG_SESSIONS
816 , eligSessionSave = 12, eligSessionResume = 13
817 #endif
818 #if CONFIG_JUMPS
819 , eligJump = 14
820 #endif
821 #if OPTION_EXECEXT & EXECEXT_SHELL
822 , eligExecextShell = 15
823 #endif
824 } my_enum2(unsigned char) tEditableLineInputGoal;
825
826 #if CONFIG_USER_QUERY
827 #define is_disguising_elig(elig) \
828 ( ((elig) == eligFormPassword) || ((elig) == eligPassword) )
829 #else
830 #define is_disguising_elig(elig) ((elig) == eligFormPassword)
831 #endif
832
833 #define is_emptyok_elig(elig) ( ((elig) == eligFormText) || \
834 ((elig) == eligFormPassword) || ((elig) == eligFormFilename) )
835
836 my_enum1 enum
837 { cgQuit = 0, cgClose = 1, cgOverwriteFile = 2,
838 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
839 cgOverwriteDownload = 3,
840 #endif
841 #if CONFIG_EXTRA & EXTRA_DUMP
842 cgOverwriteDump = 4,
843 #endif
844 #if CONFIG_SESSIONS
845 cgOverwriteSession = 5,
846 #endif
847 cgSubmit = 6, cgReset = 7, cgRepost = 8, cgHtml = 9, cgEnable = 10
848 #if CONFIG_FTP && OPTION_TLS
849 , cgFtpsDataclear = 11
850 #endif
851 } my_enum2(unsigned char) tConfirmationGoal;
852
853 static const char* khmli_filename = NULL;
854 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
855 static const char* khmli_download_uri = NULL;
856 #endif
857
858 #if CONFIG_EXTRA & EXTRA_DUMP
859
860 my_enum1 enum
861 { dsfNone = 0, dsfMustSource = 0x01, dsfMustHtml = 0x02, dsfBeepWhenDone = 0x04
862 } my_enum2(unsigned char) tDumpStyleFlags;
863
864 my_enum1 enum
865 { dscNone = 0, dscDocument = 0x01, dscLinklist = 0x02, dscImagelist = 0x04
866 } my_enum2(unsigned char) tDumpStyleContents;
867
868 typedef struct
869 { tCoordinate width;
870 unsigned char kb_hashmarks;
871 tDumpStyleFlags flags;
872 } tDumpStyle;
873
874 #if 0
875 static tDumpStyle khmli_dump_style;
876 #endif
877
878 #endif /* #if CONFIG_EXTRA & EXTRA_DUMP */
879
880 #if CONFIG_MENUS & MENUS_UHIST
881 #define URI_HISTORY_LEN (20)
882 static const char* uri_history[URI_HISTORY_LEN];
883 static unsigned short uri_history_index = 0;
884 #endif
885
886 my_enum1 enum
887 { wrtRedraw = 0, wrtRedrawRecursive = 1, wrtSearch = 2, wrtSearchBackward = 3,
888 wrtToEnd = 4
889 } my_enum2(unsigned char) tWindowRedrawingTask;
890
891 /* some prototypes */
892 static void show_message(const char*, tBoolean);
893 static void __line_input_estart(tEditableLineInputGoal, const char*,
894 const char*, tUserQuery*);
895 #define line_input_estart(a, b, c, d, e, f, g) __line_input_estart(a, b, c, d)
896
897
898 /** URI parsing */
899
900 static const struct
901 { const char* scheme; /* (sorted in strcmp() order) */
902 tResourceProtocol rp;
903 } scheme2rp[] =
904 { { strAbout, rpAbout },
905 #if CONFIG_DEBUG
906 { strCvs, rpCvs },
907 #endif
908 { strFile, rpLocal },
909 { strFinger, __rpFinger },
910 { strFtp, __rpFtp },
911 { strFtps, __rpFtps },
912 { strGopher, __rpGopher },
913 { strHttp, rpHttp },
914 { strHttps, __rpHttps },
915 #if CONFIG_DEBUG
916 { strInfo, rpInfo },
917 #endif
918 { strJavascript, __rpJavascript },
919 { strLocal, rpLocal },
920 { strLocalCgi, __rpLocalCgi },
921 { strMailto, __rpMailto },
922 { strNews, __rpNntp },
923 { strNntp, __rpNntp },
924 { strPop, __rpPop },
925 { strPops, __rpPops }
926 };
927
do_lookup_rp(const char * scheme)928 static one_caller tMbsIndex do_lookup_rp(const char* scheme)
929 { my_binary_search(0, ARRAY_ELEMNUM(scheme2rp) - 1,
930 streqcase3(scheme, scheme2rp[idx].scheme), return(idx))
931 /* case-insensitivity: RFC3986 (6.2.2.1) and RFC2616 (3.2.3) -- or was that
932 casein-sensitivity? :-) */
933 }
934
lookup_rp(const char * scheme)935 static one_caller tResourceProtocol lookup_rp(const char* scheme)
936 { const tMbsIndex idx = do_lookup_rp(scheme);
937 return ( (idx >= 0) ? scheme2rp[idx].rp : rpUnknown );
938 }
939
uri_parse_part(const char * uri,unsigned char part)940 static char* uri_parse_part(const char* uri, unsigned char part)
941 { static const char separator[] = ":/?#";
942 const char* const sep = separator + part;
943 char uch, sch; /* URI character, separator character */
944 /* The following code does very much the same as strpbrk(), but that function
945 isn't portable. */
946 while ( (uch = *uri) != '\0' )
947 { const char* temp = sep;
948 while ( (sch = *temp++) != '\0' )
949 { if (uch == sch) return(unconstify(uri)); }
950 uri++;
951 }
952 return(NULL);
953 }
954
setup_authority(char ** _uri,char ** _authority)955 static void setup_authority(/*const*/ char** _uri,
956 /*@out@*/ /*const*/ char** _authority)
957 { /*const*/ char *uri = *_uri, *pos = uri_parse_part(uri, 1), *authority;
958 if (pos != NULL) { authority = my_strndup(uri, pos - uri); uri = pos; }
959 else { authority = my_strdup(uri); TO_EOS(uri); }
960 *_uri = uri; *_authority = authority;
961 }
962
finalize_path(const char * _path)963 static __sallocator char* __callocator finalize_path(const char* _path)
964 /* transforms a path to a standard representation by removing "superfluous"
965 components */
966 { char ch, *path = my_strdup(_path), *temp, *dest, **component;
967 size_t num, maxnum, count, orig_len = strlen(path);
968 tBoolean found_tilde = falsE, is_abs = cond2boolean(*path == chDirsep),
969 is_dir = cond2boolean( (orig_len > 0) && (path[orig_len - 1] == chDirsep)),
970 is_first;
971
972 /* split the path into components */
973 temp = path; component = NULL; num = maxnum = 0;
974 splitloop:
975 while (*temp == chDirsep) temp++;
976 if (*temp != '\0') /* got a component */
977 { if (num >= maxnum)
978 { maxnum += 16;
979 component = memory_reallocate(component, maxnum * sizeof(char*),
980 mapOther);
981 }
982 component[num++] = temp;
983 while ( (*temp != chDirsep) && (*temp != '\0') ) temp++;
984 if (*temp != '\0') { *temp++ = '\0'; goto splitloop; } /* look for more */
985 }
986
987 /* remove superfluous components */
988 #define rc(x) *(component[(x)]) = '\0'
989 for (count = 0; count < num; count++)
990 { const char* str = component[count];
991 if (!strcmp(str, strDot)) rc(count);
992 else if (!found_tilde)
993 { if (!strcmp(str, strDoubleDot))
994 { rc(count);
995 if (count > 0) /* try to remove "the preceding" component */
996 { size_t cnt = count - 1;
997 while (1)
998 { if (*(component[cnt]) != '\0') { rc(cnt); break; } /* done */
999 if (cnt == 0) break; /* no removable preceding component */
1000 cnt--;
1001 }
1002 }
1003 }
1004 else if (!strcmp(str, "~"))
1005 { /* e.g. in FTP it should be possible to do "ftp://foo.org/~/../blah",
1006 which can only be interpreted by the server, not by us */
1007 found_tilde = truE;
1008 }
1009 }
1010 }
1011 #undef rc
1012
1013 /* build the new path */
1014 dest = path; is_first = truE;
1015 if (is_abs) *dest++ = chDirsep;
1016 for (count = 0; count < num; count++)
1017 { const char* src = component[count];
1018 if (*src == '\0') continue; /* removed */
1019 if (is_first) is_first = falsE;
1020 else *dest++ = chDirsep;
1021 while ( (ch = *src++) != '\0' ) *dest++ = ch;
1022 }
1023 if (is_dir)
1024 { if ( (dest <= path) || (*(dest - 1) != chDirsep) ) *dest++ = chDirsep; }
1025 *dest = '\0';
1026
1027 __dealloc(component);
1028 return(path);
1029 }
1030
1031 #define __cleanup_path \
1032 if (must_dealloc_path) { memory_deallocate(path); must_dealloc_path = falsE;}
1033
uri_set_error(tUriData * uri,tResourceError re)1034 static __my_inline void uri_set_error(tUriData* uri, tResourceError re)
1035 { if (uri->re == reFine) uri->re = re;
1036 }
1037
uri_allocate(void)1038 static __my_inline tUriData* uri_allocate(void)
1039 { return((tUriData*) memory_allocate(sizeof(tUriData), mapUriData));
1040 }
1041
uri_parse(const char * _uri,const tUriData * const orig_referrer,const char ** _fragment,const char * extra_query,unsigned char special_flags)1042 static tUriData* uri_parse(const char* _uri, const tUriData* const
1043 orig_referrer, /*@out@*/ const char** _fragment, const char* extra_query,
1044 unsigned char special_flags)
1045 /* Please note the number and position of underscore characters in the function
1046 name: it's not "u_rip_....()" although maybe it should be... */
1047 /* RFC3986 says: ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
1048 Simple in theory, but in practice it's much more complicated. Just to
1049 mention a few examples:
1050 - Colons can appear in four places in a URI: right after the scheme string,
1051 as a separator in "username:password", as a separator within a numerical
1052 IPv6 hostname, and as a portnumber indicator.
1053 - There aren't only absolute URIs to be handled, but also relative and
1054 abbreviated URIs.
1055 To distinguish all these correctly, we have to do much more than a trivial
1056 pattern matching. Let's thank all specification developers for such
1057 bogosities. (I've looked at several implementations, and none of them seems
1058 to handle all cases the right way. And the endless comments in this function
1059 might also be an indication of bad URI design concepts, similar to the HTTP
1060 cookie mess...)
1061 */
1062 { static const char strWwwDot[] = "www.", strFtpDot[] = "ftp.",
1063 strNewsDot[] = "news.", strGopherDot[] = "gopher.";
1064 tUriData* retval = uri_allocate();
1065 const tUriData* referrer = orig_referrer;
1066 tResourceProtocol rp, wouldbe_rp;
1067 char *uristart, *uri, *pos;
1068 char *scheme, *authority, *hostname, *port, *path, *query, *fragment;
1069 const char *username, *password;
1070 char *complete_query, *spfbuf_uri;
1071 tPortnumber portnumber;
1072 tResourceError uri_error;
1073 tBoolean must_dealloc_path, must_dealloc_complete_query, ua, iph, appfrag;
1074
1075 username = password = scheme = authority = hostname = port = path = query =
1076 fragment = NULL;
1077 portnumber = 0; must_dealloc_path = must_dealloc_complete_query = falsE;
1078 rp = rpUnknown;
1079
1080 /* Prepare the URI */
1081
1082 { char* dest = uristart = uri = __memory_allocate(strlen(_uri)+1, mapString);
1083 const char* src = _uri;
1084 const unsigned char* test = (const unsigned char*) _uri;
1085 unsigned char testch;
1086 while ( (testch = *test++) != '\0' )
1087 { const char urich = *src++; /* "My first name is Robert." :-) */
1088 *dest++ = (is_control_char(testch) ? '_' : urich);
1089 }
1090 *dest = '\0';
1091 }
1092
1093 if (orig_referrer != NULL)
1094 { const char ch = *uri;
1095 if ( (ch == '\0') || (ch == '#') ) /* RFC3986, 4.4 */
1096 { const char* ref_uri = orig_referrer->uri;
1097 if ( (ref_uri != NULL) && (*ref_uri != '\0') )
1098 { char* new_uri;
1099 my_spf(NULL, 0, &new_uri, strPercsPercs, ref_uri, uri);
1100 memory_deallocate(uristart); uristart = uri = my_spf_use(new_uri);
1101 }
1102 }
1103 }
1104
1105 /* Look what's explicitly given */
1106
1107 pos = uri_parse_part(uri, 0);
1108 if ( (pos != NULL) && (*pos == ':') && (pos > uri) )
1109 { /* RFC3986 (3.1) says: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
1110 Test whether it actually _is_ a scheme name or rather an abbreviated URI
1111 like "foo.org:42" or a numerical IPv6 hostname beginning like
1112 "[42:43...]". To avoid problems, we don't allow any dots in a scheme
1113 name - RFC3986 allows dots, but nobody seems to use them. This way, only
1114 abbreviated URIs like "localhost:42" could still cause problems, and we
1115 check explicitly for "localhost". These are ugly special-case hacks, but
1116 all more elegant algorithms seem to be less correct. */
1117 const char* temp;
1118 char ch;
1119 size_t len;
1120 if ( (pos[1] == '/') && (pos[2] == '/') )
1121 { /* Found the sequence "://"; accept anything in front of this as a scheme
1122 string, no matter what it actually is. This mechanism allows users to
1123 override the below plausibility tests, so they can use even the
1124 strangest schemes if they want to. (Non-standard URI schemes will be
1125 configurable in a later version, and "all power to the user" is often
1126 a good idea anyway.)
1127 */
1128 goto do_scheme;
1129 }
1130 temp = uri; ch = *temp++;
1131 if (!my_isalpha(ch)) goto no_scheme;
1132 while (temp < pos)
1133 { ch = *temp++;
1134 if ( (!my_isalnum(ch)) && (ch != '+') && (ch != '-') ) goto no_scheme;
1135 }
1136 len = pos - uri;
1137 if ( (len == 9) && (strneqcase(uri, strLocalhost, 9)) ) goto no_scheme;
1138 do_scheme: scheme = uri; *pos++ = '\0'; uri = pos; /* scheme given */
1139 no_scheme: {}
1140 }
1141
1142 if ( (uri[0] == '/') && (uri[1] == '/') ) /* authority given */
1143 { uri += 2; setup_authority(&uri, &authority);
1144 }
1145
1146 path = uri; /* a path is always given (but may be empty, of course) */
1147
1148 pos = uri_parse_part(uri, 2);
1149 if ( (pos != NULL) && (*pos == '?') ) /* query given */
1150 { *pos++ = '\0'; query = uri = pos; }
1151
1152 pos = uri_parse_part(uri, 3);
1153 if ( (pos != NULL) && (*pos == '#') ) /* fragment given */
1154 { *pos++ = '\0'; fragment = uri = pos; }
1155
1156 /* Interpret the whole stuff */
1157
1158 /* calculate protocol and possibly authority: */
1159 if (scheme != NULL) rp = lookup_rp(scheme);
1160 else if (orig_referrer != NULL) rp = orig_referrer->rp;
1161 else if (authority == NULL)
1162 { /* The URI can only be an abbreviated thing like "www.foo.org..." or a
1163 local path. */
1164 if ( (*path == chDirsep) || ( (path[0] == '.') && (path[1] == chDirsep) )
1165 || ( (path[0] == '~') && (path[1] == chDirsep) ) )
1166 { /* local scheme, no authority necessary */
1167 set_local: rp = rpLocal;
1168 }
1169 else if (strneqcase(path, strWwwDot, 4))
1170 { set_http: rp = rpHttp;
1171 handle_abbr_authority: setup_authority(&path, &authority);
1172 }
1173 else if (strneqcase(path, strFtpDot, 4))
1174 { set_ftp: rp = __rpFtp; goto handle_abbr_authority; }
1175 else if ( (strneqcase(path, strNewsDot, 5)) /* CHECKME! ||
1176 ( (strneqcase(path, strNews, 4)) &&
1177 (uri_parse_part(path, 1) == path + 4) ) */ )
1178 { rp = __rpNntp; goto handle_abbr_authority; }
1179 else if (strneqcase(path, strGopherDot, 7))
1180 { rp = __rpGopher; goto handle_abbr_authority; }
1181 else /* last resorts */
1182 { struct stat statbuf;
1183 #if CONFIG_CUSTOM_CONN
1184 if (special_flags & 1) goto set_ftp;
1185 #endif
1186 /* might be a local regular file or directory */
1187 if (my_stat(path, &statbuf) == 0)
1188 { const mode_t mode = statbuf.st_mode;
1189 if (S_ISREG(mode) || S_ISDIR(mode)) goto set_local;
1190 }
1191 /* nothing else worked, so assume it's an HTTP resource */
1192 goto set_http;
1193 }
1194 }
1195
1196 /* split the authority: */
1197 if (authority != NULL)
1198 { pos = my_strchr(authority, '@');
1199 if (pos == NULL) hostname = authority;
1200 else
1201 { *pos++ = '\0'; hostname = pos; username = authority; /* username given */
1202 pos = my_strchr(username, ':');
1203 if (pos != NULL) { *pos++ = '\0'; password = pos; } /* password given */
1204 }
1205
1206 pos = hostname; /* default start position for the below my_strchr() call */
1207 if (*hostname == '[')
1208 { /*looks like the beginning of a numeric IPv6 address notation (RFC2732)*/
1209 char* temp;
1210 if ( (hostname[1] != '\0') &&
1211 ( ( temp = my_strchr(hostname + 2, ']') ) != NULL ) )
1212 { /* yup, it is */
1213 *temp++ = '\0'; pos = temp; hostname++;
1214 retval->udf |= udfTryIpv6;
1215 }
1216 /* "else": it isn't; we assume that users know what they're doing; we
1217 don't know :-) so we pass the stuff unchanged */
1218 }
1219 pos = my_strchr(pos, ':');
1220 if (pos != NULL) { *pos++ = '\0'; port = pos; } /* port given */
1221 if (*hostname == '\0') hostname = NULL;
1222 else
1223 { if ( (rp == rpUnknown) && (scheme == NULL) && (orig_referrer == NULL) )
1224 { /* no explicit scheme, no referrer, but an explicit hostname; seems
1225 that the user entered something like "//www.foo.org" manually; very
1226 rare, but valid and thus should be supported */
1227 if (strneqcase(hostname, strWwwDot, 4)) rp = rpHttp;
1228 else if (strneqcase(hostname, strFtpDot, 4)) rp = __rpFtp;
1229 else if ( (strneqcase(hostname, strNewsDot, 5)) ||
1230 (streqcase(hostname, strNews)) )
1231 rp = __rpNntp;
1232 }
1233 hostname = my_strdup_tolower(hostname);
1234 /* case-insensitivity: RFC3986 (6.2.2.1) and RFC2616 (3.2.3) */
1235 }
1236 }
1237
1238 #if OPTION_EXECEXT & EXECEXT_SHELL
1239 if ( (rp == rpUnknown) && (special_flags & 4) ) rp = rpExecextShell;
1240 #endif
1241 wouldbe_rp = rp; rp = rp_data[wouldbe_rp].final_rp;
1242 ua = cond2boolean(rp_data[wouldbe_rp].flags & rpfUsesAuthority);
1243 iph = cond2boolean(rp_data[wouldbe_rp].flags & rpfIsPathHierarchical);
1244
1245 /* calculate the portnumber: */
1246 if (ua)
1247 { if ( (port == NULL) || (*port == '\0') )
1248 portnumber = rp2portnumber(wouldbe_rp);
1249 else
1250 { int _portnumber;
1251 my_atoi(port, &_portnumber, NULL, 99999);
1252 if ( (_portnumber <= 0) || (_portnumber > 65535) )
1253 { uri_set_error(retval, rePortnumber); portnumber = 0; }
1254 else
1255 { portnumber = (tPortnumber) htons((unsigned short) _portnumber);
1256 retval->udf |= udfGotExplicitPortnumber;
1257 }
1258 }
1259 #if CONFIG_CUSTOM_CONN
1260 if ( (special_flags & 1) && (!(retval->udf & udfGotExplicitPortnumber)) )
1261 { if (rp == rpFtp) portnumber = htons(21);
1262 #if OPTION_TLS
1263 else if (rp == rpFtps) portnumber = htons(990);
1264 #endif
1265 }
1266 #endif
1267 }
1268
1269 #if CONFIG_FTP
1270 if (is_ftplike(wouldbe_rp))
1271 { /* check for ";type=X" ending of FTP path */
1272 const char *l_username, *l_password;
1273 size_t len = strlen(path);
1274 if ( (len >= 7) && (strneqcase(path + len - 7, ";type=", 6)) )
1275 { char typech = *(path + len - 1);
1276 if (typech == 'a') retval->udf |= udfFtpTypeA;
1277 else if (typech == 'd') retval->udf |= udfFtpTypeD;
1278 else if (typech == 'i') retval->udf |= udfFtpTypeI;
1279 else goto no_ftp_type;
1280 *(path + len - 7) = '\0';
1281 no_ftp_type: {}
1282 }
1283 /* handle username and password */
1284 l_username = strAnonymous; l_password = "guest";
1285 if ( (wouldbe_rp == rpFtp) && ((username == NULL) || (password == NULL)) )
1286 { const tConfigLogin* l = ( (hostname != NULL) ? config.ftp_login : NULL );
1287 tPortnumber portnumber1 = portnumber;
1288 while (l != NULL)
1289 { tPortnumber portnumber2 = l->hosts_portnumber;
1290 if ( ( (portnumber2 == 0) || (portnumber2 == portnumber1) ) &&
1291 (my_pattern_matcher(l->hosts_pattern, hostname)) ) /* found */
1292 { l_username = l->user; l_password = l->password; break; }
1293 l = l->next;
1294 }
1295 }
1296 if (username == NULL) username = l_username;
1297 if ( (password == NULL) && (username != NULL) && (l_username != NULL) &&
1298 (!strcmp(username, l_username)) )
1299 password = l_password;
1300 }
1301 #endif
1302
1303 /* Complete the data */
1304
1305 if ( (query != NULL) && (*query == '\0') ) query = NULL;
1306 if ( (fragment != NULL) && (*fragment == '\0') ) fragment = NULL;
1307
1308 if ( (query != NULL) && (extra_query != NULL) )
1309 { my_spf(NULL, 0, &complete_query, "%s&%s", query, extra_query);
1310 must_dealloc_complete_query = truE;
1311 }
1312 else if (query != NULL) complete_query = query;
1313 else if (extra_query != NULL) complete_query = unconstify(extra_query);
1314 else complete_query = NULL;
1315
1316 if ( (wouldbe_rp == rpLocal) || (wouldbe_rp == __rpLocalCgi) )
1317 { if ( (path[0] == '~') && (path[1] == chDirsep) )
1318 { my_spf(NULL, 0, &path, strPercsPercs, get_homepath(), path + 2);
1319 must_dealloc_path = truE;
1320 }
1321 }
1322
1323 if ( (referrer != NULL) && (rp != referrer->rp) )
1324 referrer = NULL; /* can't use it */
1325
1326 if (referrer != NULL)
1327 { const char* refpath;
1328 /* duplicate any missing information by taking it from the referrer: */
1329 if (ua)
1330 { if (hostname == NULL) hostname = my_strdup(referrer->hostname);
1331 else if (strcmp(hostname, referrer->hostname))
1332 { referrer = NULL; goto done_with_referrer; } /* can't use it */
1333 }
1334 if ( (iph) && (*path != chDirsep) &&
1335 ( (refpath = referrer->path) != NULL ) )
1336 { const char* temp = my_strrchr(refpath, chDirsep);
1337 if (temp == NULL) /* "can't happen" */
1338 { __cleanup_path path = unconstify(strSlash);
1339 }
1340 else
1341 { size_t refpathsize = temp - refpath + 1, pathsize = strlen(path) + 1,
1342 size = refpathsize + pathsize;
1343 char* dest = __memory_allocate(size, mapString);
1344 my_memcpy(dest, refpath, refpathsize);
1345 my_memcpy(dest + refpathsize, path, pathsize);
1346 __cleanup_path path = dest; must_dealloc_path = truE;
1347 }
1348 }
1349 }
1350 done_with_referrer: {}
1351
1352 if ( (*path != chDirsep) && (is_locallike(wouldbe_rp)) )
1353 { char *spfbuf, *pathtemp = path;
1354 const char* prefix = my_getcwd();
1355 while (!strncmp(pathtemp, strDotSlash, 2)) pathtemp += 2; /* REMOVEME! */
1356 my_spf(NULL, 0, &spfbuf, strPercsPercs, prefix, pathtemp);
1357 __cleanup_path path = my_spf_use(spfbuf); must_dealloc_path = truE;
1358 }
1359
1360 if ( (iph) && (*path == '\0') )
1361 { __cleanup_path path = unconstify(strSlash); }
1362
1363 if (iph)
1364 { char* finpath = finalize_path(path);
1365 __cleanup_path path = finpath; must_dealloc_path = truE;
1366 }
1367
1368 switch (rp)
1369 { case rpAbout:
1370 if (*path == '\0') { __cleanup_path path = unconstify(strRetawq); }
1371 break;
1372 #if OPTION_NEWS
1373 case rpNntp:
1374 if (hostname == NULL)
1375 { const char* n = config.news_server;
1376 if (n != NULL) hostname = my_strdup(n);
1377 }
1378 break;
1379 #endif
1380 }
1381
1382 /* Compose the final URI and store everything */
1383
1384 if (ua) sprint_safe(strbuf, strPercd, ntohs(portnumber));
1385 appfrag = cond2boolean( (special_flags & 2) && (fragment != NULL) );
1386
1387 my_spf(NULL, 0, &spfbuf_uri
1388 ,
1389 /*A*/ "%s:" /* scheme */
1390 /*B*/ "%s%s%s%s" /* double slash, hostname, colon, portnumber (if ua) */
1391 /*C*/ "%s%s" /* slash (conditional), path */
1392 /*D*/ "%s%s" /* question mark, query (if any query) */
1393 /*E*/ "%s%s" /* hashmark, fragment (if appfrag) */
1394 ,
1395 /*A*/ ( ( (scheme != NULL) && (!is_rp_nice(wouldbe_rp)) ) ? scheme :
1396 /*A*/ rp_data[wouldbe_rp].scheme ),
1397 /*B*/ (ua ? strSlashSlash : strEmpty),
1398 /*B*/ ( (ua && (hostname != NULL)) ? hostname : strEmpty ),
1399 /*B*/ (ua ? ":" : strEmpty), (ua ? strbuf : strEmpty), /* portnumber */
1400 /*C*/ ( (ua && (*path != chDirsep)) ? strDirsep : strEmpty ), path,
1401 /*D*/ ( (complete_query != NULL) ? strQm : strEmpty ),
1402 /*D*/ ( (complete_query != NULL) ? complete_query : strEmpty ),
1403 /*E*/ (appfrag ? strHm : strEmpty), (appfrag ? fragment : strEmpty)
1404 );
1405
1406 retval->rp = rp;
1407 retval->uri = my_spf_use(spfbuf_uri); retval->hostname = hostname;
1408 if (must_dealloc_path) { retval->path = path; must_dealloc_path = falsE; }
1409 else retval->path = my_strdup(path);
1410 if (complete_query != NULL)
1411 { if (must_dealloc_complete_query)
1412 { retval->query = complete_query; must_dealloc_complete_query = falsE; }
1413 else retval->query = my_strdup(complete_query);
1414 }
1415 if (username != NULL) retval->username = my_strdup(username);
1416 if (password != NULL) retval->password = my_strdup(password);
1417 if (ua) retval->portnumber = portnumber;
1418
1419 if (_fragment != NULL) *_fragment = (fragment ? my_strdup(fragment) : NULL);
1420
1421 if (rp == rpUnknown) uri_error = reProtocol;
1422 else if (rp == rpDisabled) uri_error = reProtDisabled;
1423 #if CONFIG_JAVASCRIPT
1424 else if (rp == rpJavascript) uri_error = reProtDisabled;
1425 #endif
1426 else if (ua && (hostname == NULL)) uri_error = reHostname;
1427 else goto no_uri_error;
1428 uri_set_error(retval, uri_error);
1429 no_uri_error: {}
1430
1431 memory_deallocate(uristart); __dealloc(authority);
1432 if (must_dealloc_path) memory_deallocate(path);
1433 if (must_dealloc_complete_query) memory_deallocate(complete_query);
1434 return(retval);
1435 }
1436
1437 #undef __cleanup_path
1438
1439
1440 /** Windowing */
1441
1442 my_enum1 enum
1443 { wkUnknown = 0, wkBrowser = 1
1444 #if DO_WK_INFO
1445 , wkInfo = 2
1446 #endif
1447 #if DO_WK_CUSTOM_CONN
1448 , wkCustomConn = 3
1449 #endif
1450 #if DO_WK_EDITOR
1451 , wkEditor = 4
1452 #endif
1453 #if DO_WK_FILEMGR
1454 , wkFilemgr = 5
1455 #endif
1456 #if DO_WK_MAILMGR
1457 , wkMailmgr = 6
1458 #endif
1459 } my_enum2(unsigned char) tWindowKind;
1460
1461 struct tWindowSpec;
1462
1463 typedef struct tWindow /* a "virtual window" */
1464 { struct tWindow *prev, *next; /* list of all windows */
1465 const struct tWindowSpec* spec; /* specification (operations etc.) */
1466 void* wksd; /* "window-kind-specific data", "tWkFooData*" */
1467 } tWindow;
1468
1469 my_enum1 enum
1470 { wsfNone = 0, wsfWantKeyFirst = 0x01
1471 } my_enum2(unsigned char) tWindowSpecFlags;
1472
1473 struct tBrowserDocument;
1474
1475 typedef struct tWindowSpec
1476 { void (*create)(tWindow*);
1477 void (*remove)(tWindow*);
1478 void (*redraw)(const tWindow*);
1479 tBoolean (*is_precious)(const tWindow*);
1480 tBoolean (*handle_key)(tWindow*, tKey);
1481 tBoolean (*handle_pcc)(tWindow*, tProgramCommandCode);
1482 struct tBrowserDocument* (*find_currdoc)(const tWindow*);
1483 const char* (*get_info)(tWindow*, /*@out@*/ tBoolean* /*is_error*/);
1484 const char* (*get_menu_entry)(tWindow*);
1485 #if CONFIG_MENUS & MENUS_CONTEXT
1486 void (*setup_cm)(tWindow*, short*, short*, tActiveElementNumber);
1487 #define WSPEC_CM(func) (func),
1488 #else
1489 #define WSPEC_CM(func) /* nothing */
1490 #endif
1491 #if CONFIG_SESSIONS
1492 void (*save_session)(tWindow*, int /*fd*/);
1493 #define WSPEC_SESSION(func) (func),
1494 #else
1495 #define WSPEC_SESSION(func) /* nothing */
1496 #endif
1497 #if CONFIG_DO_TEXTMODEMOUSE
1498 void (*handle_mouse)(tWindow*, tCoordinate, tCoordinate, int);
1499 #define WSPEC_MOUSE(func) (func),
1500 #else
1501 #define WSPEC_MOUSE(func) /* nothing */
1502 #endif
1503 char ui_char, session_char; /* for user interface and for session files */
1504 tWindowKind kind;
1505 tWindowSpecFlags flags;
1506 } tWindowSpec; /* "window (kind) specification" */
1507
1508 static /*@null@*/ tWindow *windowlisthead = NULL, *windowlisttail = NULL;
1509
1510 #if TGC_IS_GRAPHICS
1511 static unsigned int window_counter = 0;
1512 #else
1513 static /*@relnull@*/ tWindow* visible_window_x[2] = { NULL, NULL };
1514 typedef unsigned char tVisibleWindowIndex; /* 0..1 */
1515 static tVisibleWindowIndex current_window_index_x = 0;
1516 #define current_window_x (visible_window_x[current_window_index_x])
1517 #endif /* #if TGC_IS_GRAPHICS */
1518
1519 my_enum1 enum
1520 { wvfNone = 0, wvfScreenFull = 0x01, wvfDontDrawContents = 0x02,
1521 wvfScrollingUp = 0x04, wvfHandledRedirection = 0x08, wvfPost = 0x10
1522 } my_enum2(unsigned char) tWindowViewFlags; /* CHECKME: rename! */
1523 /* wvfScreenFull implies two things: 1. the user may scroll down; 2. we needn't
1524 redraw the document when the resource handler got more content for it. */
1525
1526 my_enum1 enum
1527 { bddmAutodetect = 0, bddmSource = 1, bddmHtml = 2 /* , bddmHex = 3 */
1528 } my_enum2(unsigned char) tBrowserDocumentDisplayMode;
1529
1530 typedef struct
1531 { /* tBoolean (*handle_pcc)(struct tBrowserDocument*, tProgramCommandCode); */
1532 tBoolean (*find_coords)(const struct tBrowserDocument*, /*@out@*/ short*,
1533 /*@out@*/ short*, /*@out@*/ short*, /*@out@*/ short*);
1534 void (*display_meta1)(struct tBrowserDocument*);
1535 void (*display_meta2)(struct tBrowserDocument*);
1536 } tBrowserDocumentOps;
1537
1538 typedef signed int tLinenumber; /* ("signed" to simplify calculations) */
1539
1540 #if TGC_IS_CURSES
1541 typedef struct tActiveElementCoordinates
1542 { struct tActiveElementCoordinates* next;
1543 tLinenumber y;
1544 short x1, x2;
1545 } tActiveElementCoordinates;
1546 /* Such a list usually consists of only one entry, sometimes two (if a
1547 linebreak occurs within an active element). In very small table columns, the
1548 list might become longer - that's the main point with this structure. */
1549 #endif
1550
1551 typedef struct
1552 { char* current_text; /* for text/password/... fields */
1553 #if TGC_IS_GRAPHICS
1554 tGraphicsWidget* widget;
1555 #else
1556 tActiveElementCoordinates* aec;
1557 #endif
1558 tActiveElementFlags flags; /* only contains aefChangeable flags */
1559 } tActiveElement;
1560
1561 typedef struct tBrowserDocument
1562 { const tBrowserDocumentOps* ops;
1563 void* container; /* e.g. "tWindowView*" for window kind "browser window" */
1564 tResourceRequest* request;
1565 tCantent* cantent; /* what's displayed for this document */
1566 const char *title, *minor_html_title, *last_info, *anchor;
1567 tActiveElement* active_element;
1568 tActiveElementNumber aenum, aemax;
1569 #if !TGC_IS_GRAPHICS
1570 tActiveElementNumber aecur, former_ae;
1571 #endif
1572 tLinenumber origin_y; /* line of content in top-left corner of e.g. window */
1573 #if MIGHT_USE_SCROLL_BARS
1574 tLinenumber sbdh; /* "document height" */
1575 int sbvi; /* "validity indicator" */
1576 #endif
1577 unsigned char redirections; /* counter to avoid infinite loops */
1578 tBrowserDocumentDisplayMode bddm;
1579 tWindowViewFlags flags;
1580 } tBrowserDocument;
1581
1582 #define window_currdoc(window) (((window)->spec->find_currdoc)(window))
1583 #define current_document_x (window_currdoc(current_window_x))
1584
1585 static char* sfbuf; /* "sf": "submit, form" */
1586 static char sfconv[4];
1587 static size_t sfbuf_size, sfbuf_maxsize;
1588
1589 /* prototype */
1590 static void document_display(tBrowserDocument*, const tWindowRedrawingTask);
1591
sfbuf_reset(void)1592 static void sfbuf_reset(void)
1593 { sfbuf = NULL; sfbuf_size = sfbuf_maxsize = 0;
1594 }
1595
1596 static /* __sallocator -- not an "only" reference... */ tWindow* __callocator
window_create(const tWindowSpec * spec)1597 window_create(/*@notnull@*/ const tWindowSpec* spec)
1598 { tWindow* retval = (tWindow*) memory_allocate(sizeof(tWindow), mapWindow);
1599 #if CONFIG_DEBUG
1600 sprint_safe(strbuf, "creating window %p (%d)\n", retval, spec->kind);
1601 debugmsg(strbuf);
1602 #endif
1603 if (windowlisthead == NULL)
1604 { windowlisthead
1605 #if !TGC_IS_GRAPHICS
1606 = visible_window_x[0]
1607 #endif
1608 = retval;
1609 }
1610 if (windowlisttail != NULL)
1611 { retval->prev = windowlisttail; windowlisttail->next = retval; }
1612 windowlisttail = retval; retval->spec = spec; retval->spec->create(retval);
1613 #if TGC_IS_GRAPHICS
1614 window_counter++;
1615 #endif
1616 return(retval);
1617 }
1618
window_remove(tWindow * window)1619 static void window_remove(tWindow* window)
1620 {
1621 #if CONFIG_DEBUG
1622 sprint_safe(strbuf, "removing window %p (%d)\n", window, window->spec->kind);
1623 debugmsg(strbuf);
1624 #endif
1625 window->spec->remove(window);
1626 if (windowlisthead == window) windowlisthead = window->next;
1627 if (windowlisttail == window) windowlisttail = window->prev;
1628 if (window->prev != NULL) window->prev->next = window->next;
1629 if (window->next != NULL) window->next->prev = window->prev;
1630 memory_deallocate(window);
1631 #if TGC_IS_GRAPHICS
1632 window_counter--;
1633 if (window_counter == 0) do_quit();
1634 #endif
1635 }
1636
window_redraw(tWindow * window)1637 static __my_inline void window_redraw(tWindow* window)
1638 { window->spec->redraw(window);
1639 }
1640
window_redraw_all(void)1641 static void window_redraw_all(void)
1642 /* redraws all visible windows */
1643 {
1644 #if TGC_IS_CURSES
1645 window_redraw(visible_window_x[0]);
1646 if (visible_window_x[1] != NULL) window_redraw(visible_window_x[1]);
1647 #endif
1648 }
1649
1650 /* prototypes */
1651 static tWindow* wk_browser_create(void);
1652 static void wk_browser_prr(tWindow*, const char*, tPrrFlags,
1653 const tBrowserDocument*);
1654 static void wk_browser_reload(tWindow*);
1655 #if DO_WK_INFO
1656 static tWindow* wk_info_create(const char*, tBrowserDocumentDisplayMode,
1657 /*@out@*/ tBrowserDocument** _document);
1658 static void wk_info_collect(tWindow*, const char*);
1659 static void wk_info_finalize(tWindow*);
1660 static void wk_info_set_message(tWindow*, const char*, tBoolean);
1661 #endif
1662
1663 static tBoolean handle_command_code(tProgramCommandCode);
1664
window_is_precious(const tWindow * window)1665 static tBoolean window_is_precious(const tWindow* window)
1666 { tBoolean (*func)(const tWindow*) = window->spec->is_precious;
1667 return( (func != NULL) ? ((func)(window)) : truE );
1668 }
1669
window_hide(tWindow * window,unsigned char removal)1670 static void window_hide(tWindow* window, unsigned char removal)
1671 { /* <removal>: 0 = must not; 1 = may; 2 = must */
1672 #if TGC_IS_GRAPHICS
1673 gtk_widget_hide(GTK_WIDGET(window->ww));
1674 if (removal) window_remove(window);
1675 #else
1676 if (window == visible_window_x[0])
1677 { visible_window_x[0] = visible_window_x[1];
1678 if (visible_window_x[0] == NULL)
1679 { /* Make sure we always have at least one window on the screen */
1680 tWindow* w = window->next;
1681 if (w == NULL) w = windowlisthead;
1682 if ( (w == NULL) || (w == window) ) w = wk_browser_create();
1683 visible_window_x[0] = w;
1684 }
1685 goto xy;
1686 }
1687 else if (window == visible_window_x[1])
1688 { xy:
1689 if ( (removal == 2) || ( (removal == 1) && (!window_is_precious(window)) ))
1690 window_remove(window);
1691 visible_window_x[1] = NULL; current_window_index_x = 0;
1692 window_redraw_all();
1693 }
1694 /* "else": nothing to do */
1695 #endif
1696 }
1697
1698 #if TGC_IS_GRAPHICS
1699 #define window_is_visible(window) (truE)
1700 #else
window_is_visible(tWindow * window)1701 static tBoolean window_is_visible(tWindow* window)
1702 { tBoolean retval;
1703 if (window == NULL) retval = falsE; /* "should not happen" */
1704 else if ((window == visible_window_x[0]) || (window == visible_window_x[1]))
1705 retval = truE;
1706 else retval = falsE;
1707 return(retval);
1708 }
1709 #endif
1710
1711 #define is_browser_window(window) ((window)->spec->kind == wkBrowser)
1712
require_browser_window(void)1713 static tWindow* require_browser_window(void)
1714 { tWindow* window = current_window_x;
1715 if (!is_browser_window(window))
1716 window = current_window_x = wk_browser_create();
1717 return(window);
1718 }
1719
redraw_message_line(void)1720 static void redraw_message_line(void)
1721 {
1722 #if TGC_IS_CURSES
1723 tWindow* window = current_window_x;
1724 tBoolean is_error;
1725 const char* str;
1726 const char* (*func)(tWindow*, tBoolean*) = window->spec->get_info;
1727 if (func != NULL) str = (func)(window, &is_error);
1728 else { str = NULL; is_error = falsE; }
1729 show_message(null2empty(str), is_error);
1730 #endif
1731 }
1732
window_contents_minmaxrow(const tWindow * window,short * _minrow,short * _maxrow)1733 static tBoolean window_contents_minmaxrow(const tWindow* window,
1734 /*@out@*/ short* _minrow, /*@out@*/ short* _maxrow)
1735 { tBoolean retval;
1736 short minrow = 0, maxrow = MAX(LINES - 3, 0);
1737 if (window == visible_window_x[0])
1738 { if (visible_window_x[1] != NULL) /* only use top-half of screen */
1739 maxrow = VMIDDLE - 1;
1740 it_worked: *_minrow = minrow; *_maxrow = maxrow; retval = truE;
1741 }
1742 else if (window == visible_window_x[1]) /* only use bottom-half of screen */
1743 { minrow = VMIDDLE + 1; goto it_worked; }
1744 else retval = falsE; /* "should not happen" */
1745 return(retval);
1746 }
1747
1748
1749 /** Message display */
1750
show_message(const char * msg,tBoolean is_error)1751 static void show_message(const char* msg, tBoolean is_error)
1752 {
1753 #if TGC_IS_GRAPHICS
1754 tGraphicsWidget* widget = current_window->message;
1755 gtk_label_set_text(GTK_LABEL(widget), msg);
1756 #else
1757 if (!is_bottom_occupied)
1758 { if (is_error) my_set_color(cpnRed);
1759 (void) mvaddnstr(LINES - 1, 0, msg, COLS - 1);
1760 if (is_error) my_set_color(cpnDefault);
1761 (void) clrtoeol(); must_reset_cursor();
1762 }
1763 #endif
1764 debugmsg("UI message: "); debugmsg(msg); debugmsg(strNewline);
1765 }
1766
show_message_osfailure(int errnum,const char * text)1767 static void show_message_osfailure(int errnum, const char* text)
1768 { if (text == NULL) text = _("Failed!"); /* some stupidistic default text */
1769 if (errnum > 0)
1770 { const char* e = my_strerror(errnum);
1771 if (strlen(e) > STRBUF_SIZE / 2) e = _(strUnknown); /* can't be serious */
1772 sprint_safe(strbuf, _("%s - error #%d, %s"), text, errnum, e);
1773 text = strbuf; /* CHECKME: use strErrorTrail[]! */
1774 }
1775 show_message(text, truE);
1776 }
1777
1778 #if CONFIG_CONSOLE
cc_output_str(const char * str)1779 static __my_inline void cc_output_str(const char* str)
1780 { my_write_str(fd_stdout, str); /* FIXME for window! */
1781 }
1782 #define cc_output_errstr(str) cc_output_str(str) /* (_currently_ the same) */
1783 #endif
1784
inform_about_error(int errnum,const char * text)1785 static void inform_about_error(int errnum, const char* text)
1786 { if (is_environed) show_message_osfailure(errnum, text);
1787 #if CONFIG_CONSOLE
1788 else if (program_mode == pmConsole)
1789 { cc_output_errstr(text);
1790 if (errnum > 0)
1791 { char buf[1000];
1792 const char* errstr = my_strerror(errnum);
1793 if (strlen(errstr) > 200) errstr = _(strUnknown);
1794 sprint_safe(buf, _(strErrorTrail), errnum, errstr);
1795 cc_output_errstr(buf);
1796 }
1797 cc_output_errstr(strNewline);
1798 }
1799 #endif
1800 else fatal_error(errnum, text);
1801 }
1802
1803 #if ((CONFIG_MENUS & MENUS_ALL) != MENUS_ALL) || (!CONFIG_SESSIONS) || (!CONFIG_JUMPS) || (!CONFIG_DO_TEXTMODEMOUSE) || (!OPTION_EXECEXT) || (!MIGHT_USE_SCROLL_BARS) || (!(CONFIG_EXTRA & EXTRA_DOWNLOAD)) || (!(CONFIG_EXTRA & EXTRA_DUMP)) || (!DO_WK_INFO) /* (-: */
fwdact(const char * str)1804 static void fwdact(const char* str)
1805 { char* spfbuf;
1806 if (*str == 'F') str++;
1807 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _(strFwdact), str, strEmpty);
1808 *spfbuf = my_toupper(*spfbuf);
1809 show_message(spfbuf, truE);
1810 my_spf_cleanup(strbuf, spfbuf);
1811 }
1812 #endif
1813
1814 #if (CONFIG_MENUS & MENUS_ALL) != MENUS_ALL
menus_were_disabled(void)1815 static void menus_were_disabled(void)
1816 { fwdact(_("Fthis kind of menus"));
1817 }
1818 #endif
1819
1820 #if TGC_IS_CURSES
terminal_too_small(void)1821 static my_inline void terminal_too_small(void)
1822 { show_message(_("Terminal too small"), truE);
1823 }
1824 #endif
1825
1826
1827 /** Resource requests */
1828
request_dhm_control(__sunused void * _request __cunused,__sunused void * data __cunused,__sunused tDhmControlCode dcc __cunused)1829 static void request_dhm_control(__sunused void* _request __cunused, __sunused
1830 void* data __cunused, __sunused tDhmControlCode dcc __cunused)
1831 /* just a dummy, at least for now */
1832 { /* tResourceRequest* request = (tResourceRequest*) _request; */
1833 }
1834
request_create(void)1835 static __sallocator tResourceRequest* __callocator request_create(void)
1836 { tResourceRequest* retval = (tResourceRequest*)
1837 memory_allocate(sizeof(tResourceRequest), mapResourceRequest);
1838 #if CONFIG_DEBUG
1839 sprint_safe(debugstrbuf, "request_create(): %p\n", retval);
1840 debugmsg(debugstrbuf);
1841 #endif
1842 dhm_init(retval, request_dhm_control, "request");
1843 return(retval);
1844 }
1845
request_remove(tResourceRequest * request)1846 static void request_remove(tResourceRequest* request)
1847 {
1848 #if CONFIG_DEBUG
1849 sprint_safe(debugstrbuf, "request_remove(): %p\n", request);
1850 debugmsg(debugstrbuf);
1851 #endif
1852 dhm_notify(request, dhmnfRemoval);
1853 if (request->resource != NULL) dhm_detach(request->resource);
1854 if (request->uri_data != NULL) uri_detach(request->uri_data);
1855 sinking_data_deallocate(&(request->sinking_data));
1856 memory_deallocate(request);
1857 }
1858
request_set_error(tResourceRequest * request,tResourceError error)1859 static void request_set_error(tResourceRequest* request, tResourceError error)
1860 { if (request->state != rrsError)
1861 { request->state = rrsError; request->error = error; }
1862 }
1863
request_copy_error(tResourceRequest * request,const tResource * resource)1864 static void request_copy_error(tResourceRequest* request,
1865 const tResource* resource)
1866 /* CHECKME! */
1867 { if ( (resource->state == rsError) && (request->state != rrsError) )
1868 { request->state = rrsError; request->error = resource->error; }
1869 }
1870
request_queue(tResourceRequest * request,tResourceRequestAction action)1871 static void request_queue(tResourceRequest* request,
1872 tResourceRequestAction action)
1873 /* feeds the request to the resource handler for kickoff */
1874 { request->action = action; request->state = rrsPreparedByMain;
1875 #if CONFIG_DEBUG
1876 { const tUriData* u = request->uri_data;
1877 char* spfbuf;
1878 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
1879 "Queueing request: %d - *%s* - *%s* - *%s*\n", u->rp, null2empty(u->uri),
1880 null2empty(u->query), null2empty(u->post));
1881 debugmsg(spfbuf); my_spf_cleanup(strbuf, spfbuf);
1882 }
1883 #endif
1884 resource_request_start(request);
1885 }
1886
1887 typedef struct
1888 { const char* uri; /*I*/
1889 const tBrowserDocument* referrer; /*I*/
1890 tResourceRequest* result; /*O*/
1891 const char* uri_anchor; /*O*/ /* (if prrfWantUriAnchor) */
1892 tPrrFlags prrf; /*I*/
1893 } tPrrData; /* I=input, O=output */
1894
prr_setup(tPrrData * data,const char * uri,tPrrFlags prrf)1895 static my_inline void prr_setup(/*@out@*/ tPrrData* data, const char* uri,
1896 tPrrFlags prrf)
1897 { my_memclr(data, sizeof(tPrrData)); data->uri = uri; data->prrf = prrf;
1898 }
1899
prr_setdown(tPrrData * data)1900 static void prr_setdown(tPrrData* data)
1901 /* removes things which were allocated by prepare_resource_request() but
1902 not "consumed" by its caller */
1903 { tResourceRequest* request = data->result;
1904 if (request != NULL) request_remove(request);
1905 __dealloc(data->uri_anchor);
1906 }
1907
prepare_resource_request(tPrrData * data)1908 static void prepare_resource_request(tPrrData* data)
1909 { tResourceRequest* request = data->result = request_create();
1910 const tBrowserDocument* _referrer = data->referrer;
1911 const tResourceRequest* referrer = (_referrer ? _referrer->request : NULL);
1912 const tUriData* ref_ud = (referrer ? referrer->uri_data : NULL);
1913 const char *sfbuf_final, *extra_query;
1914 const tPrrFlags prrf = data->prrf;
1915 tUriData* uri_data;
1916
1917 if (sfbuf == NULL) sfbuf_final = NULL;
1918 else
1919 { sfbuf_final = sfbuf;
1920 if (*sfbuf_final == '&') sfbuf_final++;
1921 if (*sfbuf_final == '\0') sfbuf_final = NULL; /* no "real" data */
1922 }
1923 if ( (!(prrf & prrfPost)) && (prrf & prrfUseSfbuf) && (sfbuf_final != NULL) )
1924 { /* not a post request, but still some submit-form data, thus query */
1925 extra_query = sfbuf_final;
1926 }
1927 else extra_query = NULL;
1928
1929 uri_data = uri_parse(data->uri, ref_ud, ( (prrf & prrfWantUriAnchor) ?
1930 (&(data->uri_anchor)) : NULL ), extra_query, ( (prrf & prrfUpsfp4) ? 4:0));
1931 uri_attach(request->uri_data, uri_data);
1932 if (uri_data->re != reFine) request_set_error(request, uri_data->re);
1933
1934 if (prrf & prrfPost)
1935 { request->flags |= rrfPost;
1936 if ( (prrf & prrfUseSfbuf) && (sfbuf_final != NULL) )
1937 request->uri_data->post = my_strdup(sfbuf_final);
1938 }
1939 if (sfbuf != NULL) { memory_deallocate(sfbuf); sfbuf_reset(); }
1940
1941 if ( (prrf & prrfIsRedirection) ||
1942 ( (_referrer != NULL) && (_referrer->redirections > 0) ) )
1943 { request->flags |= rrfIsRedirection; }
1944 }
1945
calculate_reqresmsg(const tResourceRequest * request,const tResource * resource,unsigned char flags,tBoolean * _is_error)1946 static const char* calculate_reqresmsg(const tResourceRequest* request,
1947 const tResource* resource, unsigned char flags, /*@out@*/tBoolean* _is_error)
1948 { const char* retval;
1949 *_is_error = falsE; /* default */
1950 if (resource != NULL)
1951 { const tCantent* cantent = resource->cantent;
1952 const tResourceState rs = resource->state;
1953 const tServerStatusCode ssc = resource->server_status_code;
1954 if (rs == rsError)
1955 { const tResourceError re = resource->error;
1956 #if OPTION_TLS
1957 if ((re == reTls) && (tls_errtext(resource, strbuf2))) retval = strbuf2;
1958 else
1959 #endif
1960 { retval = _(strResourceError[re]); }
1961 *_is_error = truE;
1962 }
1963 #if OPTION_TLS
1964 else if (resource_in_tls(resource, truE)) retval = _("TLS handshaking");
1965 #endif
1966 else if (rs == rsReading)
1967 { const size_t count = resource->bytecount;
1968 if (count < 2) retval = _("Waiting for reply");
1969 else
1970 { const size_t nominal = resource->nominal_contentlength;
1971 const char* temp;
1972 if ( (nominal != UNKNOWN_CONTENTLENGTH) && (count < nominal) )
1973 { sprint_safe(strbuf3, _("of %d "), localized_size(nominal));
1974 temp = strbuf3;
1975 }
1976 else temp = strEmpty;
1977 sprint_safe(strbuf2, _("Received %d %sbytes"), localized_size(count),
1978 temp);
1979 retval = strbuf2;
1980 }
1981 }
1982 else if ( (!(flags & 1)) && (rs == rsComplete) && ( (cantent->content ==
1983 NULL) || (cantent->content->used <= 0) ) )
1984 { retval = _("Document empty"); }
1985 else if (resource_ui_conn_ip(resource, strbuf2, STRBUF_SIZE / 2))
1986 { sprint_safe(strbuf3, _("Connecting to %s"), strbuf2); retval = strbuf3; }
1987 else retval = _(strResourceState[rs]);
1988 #if CONFIG_DEBUG
1989 sprint_safe(strbuf,
1990 "%s [res=%p,rs=%d,re=%d,rch=%d,rf=%d,ths=%d,ssc=%d,ssi=*%s*]", retval,
1991 resource, rs, resource->error, resource->handshake, resource->flags,
1992 resource->tlheaderstate, ssc, ssc2info(resource->protocol, ssc));
1993 retval = strbuf;
1994 #else
1995 if (ssc != 0)
1996 { const tResourceProtocol rp = resource->protocol;
1997 const char *ssi = ssc2info(rp, ssc), *ssi_sep = ( (ssi == strEmpty) ?
1998 strEmpty : strSpacedDash ), *whose;
1999 #if OPTION_LOCAL_CGI
2000 if (rp == rpLocalCgi) whose = _("script status: ");
2001 else
2002 #endif
2003 { whose = _("server status: "); }
2004 sprint_safe(strbuf, "%s (%s%d%s%s)", retval, whose, ssc, ssi_sep, ssi);
2005 retval = strbuf;
2006 }
2007 #endif
2008 }
2009 else if (request != NULL)
2010 { const tResourceRequestState rrs = request->state;
2011 switch (rrs)
2012 { case rrsError:
2013 retval = _(strResourceError[request->error]); *_is_error = truE; break;
2014 case rrsUnknown: retval = strEmpty; break;
2015 default: retval = _(strResourceRequestState[rrs]); break;
2016 }
2017 }
2018 else retval = strEmpty;
2019 return(retval);
2020 }
2021
2022
2023 /** Browser documents */
2024
2025 /* Browser documents: screen coordinates */
2026
2027 #if TGC_IS_GRAPHICS
2028
document_section_height(const tBrowserDocument * document)2029 static my_inline tLinenumber document_section_height(const tBrowserDocument*
2030 document)
2031 { return(LINES); /* FIXME! */
2032 }
2033
calculate_minmaxrow(const tBrowserDocument * document __cunused,short * min,short * max)2034 static my_inline void calculate_minmaxrow(const tBrowserDocument* document
2035 __cunused, /*@out@*/ short* min, /*@out@*/ short* max)
2036 { *min = 0; *max = LINES - 1; /* FIXME! */
2037 }
2038
2039 #else
2040
document_find_coords(const tBrowserDocument * document,short * _x1,short * _y1,short * _x2,short * _y2)2041 static __my_inline tBoolean document_find_coords(const tBrowserDocument*
2042 document, /*@out@*/ short* _x1, /*@out@*/ short* _y1, /*@out@*/ short* _x2,
2043 /*@out@*/ short* _y2)
2044 { return((document->ops->find_coords)(document, _x1, _y1, _x2, _y2));
2045 }
2046
document_section_height(const tBrowserDocument * document,tLinenumber * _height)2047 static tBoolean document_section_height(const tBrowserDocument* document,
2048 /*@out@*/ tLinenumber* _height)
2049 { short x1, y1, x2, y2;
2050 const tBoolean retval = document_find_coords(document, &x1, &y1, &x2, &y2);
2051 if (retval) *_height = y2 - y1 + 1;
2052 return(retval);
2053 }
2054
2055 #if (CONFIG_TG == TG_XCURSES) && (MIGHT_USE_SCROLL_BARS)
document_minmaxcol(__sunused const tBrowserDocument * document __cunused,short * _mincol,short * _maxcol)2056 static tBoolean document_minmaxcol(__sunused const tBrowserDocument* document
2057 __cunused, /*@out@*/ short* _mincol, /*@out@*/ short* _maxcol)
2058 { *_mincol = 0; *_maxcol = COLS - 1; /* FIXME for framesets etc.! */
2059 return(truE);
2060 }
2061 #endif
2062
document_minmaxrow(const tBrowserDocument * document,short * _minrow,short * _maxrow)2063 static tBoolean document_minmaxrow(const tBrowserDocument* document,
2064 /*@out@*/ short* _minrow, /*@out@*/ short* _maxrow)
2065 { short x1, y1, x2, y2;
2066 tBoolean retval = document_find_coords(document, &x1, &y1, &x2, &y2);
2067 if (retval) { *_minrow = y1; *_maxrow = y2; }
2068 return(retval);
2069 }
2070
document_line2row(const tBrowserDocument * document,tLinenumber y,short * _row)2071 static tBoolean document_line2row(const tBrowserDocument* document,
2072 tLinenumber y, /*@out@*/ short* _row)
2073 { short x1, y1, x2, y2;
2074 tBoolean retval = document_find_coords(document, &x1, &y1, &x2, &y2);
2075 if (retval) { *_row = y1 + ((short) (y - document->origin_y)); }
2076 return(retval);
2077 }
2078
2079 #endif
2080
2081
2082 /* Browser documents: active elements */
2083
deallocate_aec(tActiveElementCoordinates ** _aec)2084 static void deallocate_aec(/*const*/ tActiveElementCoordinates** _aec)
2085 { const tActiveElementCoordinates* aec = *_aec;
2086 if (aec == NULL) return; /* nothing to do */
2087 while (aec != NULL)
2088 { const tActiveElementCoordinates* aec_next = aec->next;
2089 memory_deallocate(aec); aec = aec_next;
2090 }
2091 *_aec = NULL;
2092 }
2093
__init_ae(const tActiveElementBase * aeb,tActiveElement * ae,tBoolean is_first_init,tWindowView * view)2094 static void __init_ae(const tActiveElementBase* aeb, tActiveElement* ae,
2095 tBoolean is_first_init
2096 #if TGC_IS_GRAPHICS
2097 , tWindowView* view
2098 #endif
2099 )
2100 { const tActiveElementKind aek = aeb->kind;
2101 const char* text;
2102
2103 if (is_first_init) my_memclr(ae, sizeof(tActiveElement));
2104 else dealloc(ae->current_text);
2105 ae->flags = aeb->flags & aefChangeable;
2106
2107 if ((aek == aekFormText) || (aek == aekFormPassword) || (aek == aekFormFile))
2108 { if ( (text = aeb->render) != NULL ) ae->current_text = my_strdup(text);
2109 }
2110 else if (aek == aekFormSelect)
2111 { const tHtmlInputLength count = aeb->maxlength;
2112 if (count > 0) /* non-empty selection list */
2113 { char* t = ae->current_text = memory_allocate((count + 7) / 8, mapOther);
2114 const tHtmlOption* o = (const tHtmlOption*) aeb->render;
2115 tHtmlInputLength c = 0;
2116 while (o != NULL)
2117 { if (o->flags & hofSelected) my_bit_set(t, c);
2118 o = o->next; c++;
2119 }
2120 }
2121 }
2122
2123 #if TGC_IS_GRAPHICS
2124 if (is_first_init) /* possibly create a widget */
2125 { tGraphicsWidget* w;
2126 tHtmlInputLength maxlength;
2127 switch (aek)
2128 { case aekFormText: case aekFormPassword: case aekFormTextarea:
2129 text = aeb->render;
2130 maxlength = (has_input_length(aek) ? aeb->maxlength : 0);
2131 /* FIXME: don't show passwords! */
2132 /* w = gtk_text_new(NULL, NULL); */
2133 w = ( (maxlength > 0) ? gtk_entry_new_with_max_length(maxlength) :
2134 gtk_entry_new() );
2135 if ( (text != NULL) && (*text != '\0') )
2136 gtk_entry_set_text(GTK_ENTRY(w), text);
2137 break;
2138 case aekFormCheckbox: w = gtk_check_button_new(); break;
2139 case aekFormRadio: w = gtk_radio_button_new(NULL); break;/*IMPLEMENTME!*/
2140 case aekFormFile: w = gtk_file_selection_new(strEmpty); break;
2141 default: w = NULL; break;
2142 }
2143 /* FIXME: check whether disabled, readonly, ...! */
2144 if (w != NULL)
2145 { pack_box(view->window->contents, w); show_widget(w); ae->widget = w; }
2146 }
2147 #endif
2148 }
2149
2150 #if TGC_IS_GRAPHICS
2151 #define init_ae(a, b, c, d) __init_ae(a, b, c, d)
2152 #else
2153 #define init_ae(a, b, c, d) __init_ae(a, b, c)
2154 #endif
2155
2156 #if !TGC_IS_GRAPHICS
2157
2158 static const tActiveElementCoordinates*
find_visible_aec(const tBrowserDocument * document,tActiveElementNumber _ae)2159 find_visible_aec(const tBrowserDocument* document, tActiveElementNumber _ae)
2160 /* tries to find visible coordinates for the given active element */
2161 { const tActiveElementCoordinates *retval = NULL,
2162 *aec = document->active_element[_ae].aec;
2163 tLinenumber yvis1, yvis2;
2164 short minrow, maxrow;
2165
2166 if (aec == NULL) goto out; /* no coordinates, nothing visible */
2167 if (!document_minmaxrow(document, &minrow, &maxrow)) goto out;
2168 yvis1 = document->origin_y; yvis2 = yvis1 + (maxrow - minrow);
2169 while (aec != NULL)
2170 { tLinenumber y = aec->y;
2171 if ( (y >= yvis1) && (y <= yvis2) ) { retval = aec; break; }
2172 aec = aec->next;
2173 }
2174 out:
2175 return(retval);
2176 }
2177
is_ae_visible(const tBrowserDocument * document,tActiveElementNumber _ae)2178 static __my_inline tBoolean is_ae_visible(const tBrowserDocument* document,
2179 tActiveElementNumber _ae)
2180 { return(cond2boolean(find_visible_aec(document, _ae) != NULL));
2181 }
2182
next_visible_ae(const tBrowserDocument * document,tActiveElementNumber _ae)2183 static tActiveElementNumber next_visible_ae(const tBrowserDocument* document,
2184 tActiveElementNumber _ae)
2185 { tActiveElementNumber retval = INVALID_AE, l = document->aenum;
2186 while (++_ae < l)
2187 { if (is_ae_visible(document, _ae)) { retval = _ae; break; } }
2188 return(retval);
2189 }
2190
previous_visible_ae(const tBrowserDocument * document,tActiveElementNumber _ae)2191 static tActiveElementNumber previous_visible_ae(const tBrowserDocument*
2192 document, tActiveElementNumber _ae)
2193 { tActiveElementNumber retval = INVALID_AE;
2194 while (--_ae >= 0)
2195 { if (is_ae_visible(document, _ae)) { retval = _ae; break; } }
2196 return(retval);
2197 }
2198
do_activate_element(tBrowserDocument * document,tActiveElementNumber _ae,tBoolean active)2199 static void do_activate_element(tBrowserDocument* document,
2200 tActiveElementNumber _ae, tBoolean active)
2201 { const tActiveElementCoordinates* aec = document->active_element[_ae].aec;
2202 attr_t attribute;
2203 short minrow, maxrow, row;
2204 tLinenumber y1, y2;
2205 if (aec == NULL) goto finish;
2206 if (!document_section_height(document, &y2)) goto finish;
2207 y1 = document->origin_y; y2 += y1 - 1;
2208 attribute = (active ? A_REVERSE : A_UNDERLINE);
2209 document_minmaxrow(document, &minrow, &maxrow);
2210 while (aec != NULL)
2211 { tLinenumber y = aec->y;
2212 short x1, x2, count;
2213 if (y < y1) goto do_next;
2214 else if (y > y2) break; /* done */
2215 if (!document_line2row(document, y, &row)) break; /* "should not happen" */
2216 x1 = aec->x1; x2 = aec->x2; count = x2 - x1 + 1; (void) move(row, x1);
2217 while (count-- > 0)
2218 (void) addch((inch() & ~(A_REVERSE | A_UNDERLINE)) | attribute);
2219 do_next:
2220 aec = aec->next;
2221 }
2222 finish:
2223 if (active) document->aecur = document->former_ae = _ae;
2224 else document->aecur = INVALID_AE;
2225 }
2226
activate_element(tBrowserDocument * document,tActiveElementNumber _ae)2227 static void activate_element(tBrowserDocument* document,
2228 tActiveElementNumber _ae)
2229 { tActiveElementNumber old = document->aecur;
2230 if (old != INVALID_AE)
2231 do_activate_element(document, old, falsE); /* deactivate old */
2232 do_activate_element(document, _ae, truE); /* activate new */
2233 must_reset_cursor();
2234 }
2235
2236 #endif /* #if TGC_IS_GRAPHICS */
2237
2238
2239 /* Browser documents: general */
2240
document_init(tBrowserDocument * document,const tBrowserDocumentOps * ops,void * container,tResourceRequest * request)2241 static my_inline void document_init(tBrowserDocument* document,
2242 const tBrowserDocumentOps* ops, void* container, tResourceRequest* request)
2243 { document->ops = ops; document->container = container;
2244 document->request = request;
2245 #if !TGC_IS_GRAPHICS
2246 document->aecur = document->former_ae = INVALID_AE;
2247 #endif
2248 }
2249
document_tear(const tBrowserDocument * document)2250 static void document_tear(const tBrowserDocument* document)
2251 /* IMPORTANT: the <document> itself must be deallocated by the _caller_ (if
2252 necessary at all)! */
2253 { tResourceRequest* request = document->request;
2254 tCantent* cantent = document->cantent;
2255 if (request != NULL) request_remove(request);
2256 if (cantent != NULL) cantent_put(cantent);
2257 __dealloc(document->title); __dealloc(document->minor_html_title);
2258 __dealloc(document->last_info); __dealloc(document->anchor);
2259 if (document->aenum > 0)
2260 { /*const*/ tActiveElement* aes = document->active_element;
2261 tActiveElementNumber _ae;
2262 for (_ae = 0; _ae < document->aenum; _ae++)
2263 { __dealloc(aes[_ae].current_text);
2264 #if TGC_IS_GRAPHICS
2265 /* FIXME: handle widget! */
2266 #else
2267 deallocate_aec(&(aes[_ae].aec));
2268 #endif
2269 }
2270 memory_deallocate(aes);
2271 }
2272 }
2273
2274
2275 /* Browser documents: HTML forms */
2276
__sfbuf_add(const char ch)2277 static void __sfbuf_add(const char ch)
2278 /* append character unconverted */
2279 { if (sfbuf_maxsize <= sfbuf_size)
2280 { sfbuf_maxsize += 100;
2281 sfbuf = memory_reallocate(sfbuf, sfbuf_maxsize, mapString);
2282 }
2283 sfbuf[sfbuf_size++] = ch;
2284 }
2285
__sfbuf_add_str(const char * str)2286 static my_inline void __sfbuf_add_str(const char* str)
2287 /* append string unconverted */
2288 { char ch;
2289 while ( (ch = *str++) != '\0' ) __sfbuf_add(ch);
2290 }
2291
sfbuf_add_str(const char * str)2292 static void sfbuf_add_str(const char* str)
2293 /* append string converted */
2294 { char ch;
2295 while ( (ch = *str++) != '\0' )
2296 { /* append character converted */
2297 { if (my_isalnum(ch)) __sfbuf_add(ch);
2298 else if (ch == ' ') __sfbuf_add('+');
2299 else if (ch == '\n') __sfbuf_add_str("%0d%0a");
2300 else if (ch != '\r')
2301 { sfconv[1] = strHexnum[(ch >> 4) & 15]; sfconv[2] = strHexnum[ch & 15];
2302 __sfbuf_add_str(sfconv);
2303 }
2304 }
2305 }
2306 }
2307
calc_hfn(const tCantent * cantent,tActiveElementNumber _ae)2308 static tHtmlFormNumber calc_hfn(const tCantent* cantent,
2309 tActiveElementNumber _ae)
2310 /* searches for the number of the HTML form to which the <_ae> belongs */
2311 { tHtmlFormNumber retval = INVALID_HTML_FORM_NUMBER, hfn, hfnum;
2312 const tHtmlForm* form;
2313 if ( (_ae == INVALID_AE) || (cantent == NULL) ||
2314 ( (hfnum = cantent->hfnum) <= 0 ) )
2315 { goto out; } /* can't find anything */
2316 form = cantent->form;
2317 for (hfn = 0; hfn < hfnum; hfn++)
2318 { tActiveElementNumber a1 = form[hfn].first_ae, a2 = form[hfn].last_ae;
2319 if ((a1 != INVALID_AE) && (a2 != INVALID_AE) && (_ae >= a1) && (_ae <= a2))
2320 { retval = hfn; goto out; } /* found */
2321 }
2322 out:
2323 return(retval);
2324 }
2325
cnsniaf(void)2326 static void cnsniaf(void)
2327 { show_message(_("Can't submit - not inside a form?"), truE);
2328 }
2329
document_form_submit(const tBrowserDocument * document)2330 static void document_form_submit(/*@notnull@*/ const tBrowserDocument*document)
2331 { const tCantent* cantent = document->cantent;
2332 const tActiveElementBase* aebase;
2333 tActiveElementNumber _ae, a1, a2;
2334 const tActiveElement* aes;
2335 #if TGC_IS_GRAPHICS
2336 const tActiveElementNumber aecur = INVALID_AE; /* FIXME! */
2337 const tHtmlFormNumber hfn = INVALID_HTML_FORM_NUMBER; /* FIXME! */
2338 #else
2339 const tActiveElementNumber aecur = document->aecur;
2340 const tHtmlFormNumber hfn = calc_hfn(cantent, aecur);
2341 #endif
2342 const tHtmlOption* o;
2343 tHtmlOptionNumber onum;
2344 tPrrFlags prrf;
2345
2346 if ( (cantent == NULL) || (hfn == INVALID_HTML_FORM_NUMBER) )
2347 { cant_submit: cnsniaf(); return; }
2348 a1 = cantent->form[hfn].first_ae; a2 = cantent->form[hfn].last_ae;
2349 if ( (a1 == INVALID_AE) || (a2 == INVALID_AE) ) goto cant_submit;
2350
2351 javascript_handle_event(jekSubmit, aecur); sfbuf_reset();
2352 aebase = cantent->aebase; aes = document->active_element;
2353 for (_ae = a1; _ae <= a2; _ae++)
2354 { tActiveElementKind kind;
2355 const char *name, *value, *bitfield;
2356 tBoolean is_first, is_multiple;
2357 if (aebase[_ae].flags & aefDisabled) continue;
2358 kind = aebase[_ae].kind;
2359 if ( (!is_form_aek(kind)) || ( (name = aebase[_ae].data) == NULL )
2360 || (*name == '\0') )
2361 { continue; } /* non-form element or no element name - unusable here */
2362 switch (kind)
2363 {case aekFormText: case aekFormPassword: case aekFormTextarea:
2364 value = aes[_ae].current_text;
2365 append:
2366 if (value != NULL)
2367 { __sfbuf_add('&'); sfbuf_add_str(name);
2368 __sfbuf_add('='); sfbuf_add_str(value);
2369 }
2370 break;
2371 case aekFormCheckbox:
2372 if (aes[_ae].flags & aefCheckedSelected) { value = strOn; goto append; }
2373 break;
2374 case aekFormRadio:
2375 if (aes[_ae].flags & aefCheckedSelected)
2376 { value = aebase[_ae].render; goto append; }
2377 break;
2378 case aekFormSubmit: case aekFormImage:
2379 if (_ae == aecur)
2380 { if (kind == aekFormImage)
2381 { __sfbuf_add('&'); sfbuf_add_str(name); __sfbuf_add_str(".x=0&");
2382 sfbuf_add_str(name); __sfbuf_add_str(".y=0"); continue;
2383 }
2384 else if (aebase[_ae].flags & aefButtonTag) value = aebase[_ae].render;
2385 else value = strOn;
2386 goto append;
2387 }
2388 break;
2389 case aekFormHidden:
2390 value = aebase[_ae].render; goto append; /*@notreached@*/ break;
2391 case aekFormSelect:
2392 bitfield = aes[_ae].current_text;
2393 o = (const tHtmlOption*) aebase[_ae].render; onum = 0; is_first = truE;
2394 is_multiple = cond2boolean(aebase[_ae].flags & aefMultiple);
2395 while (o != NULL)
2396 { if (my_bit_test(bitfield, onum)) /* option is selected */
2397 { if (!is_first) __sfbuf_add(',');
2398 else
2399 { __sfbuf_add('&'); sfbuf_add_str(name); __sfbuf_add('=');
2400 is_first = falsE;
2401 }
2402 sfbuf_add_str(o->value);
2403 if (!is_multiple) break; /* done with collecting */
2404 }
2405 o = o->next; onum++;
2406 }
2407 break;
2408 /* case aekFormReset: break; -- nothing to do, element can't succeed */
2409 /* case aekFormFile: break; -- IMPLEMENTME! */
2410 }
2411 }
2412 __sfbuf_add('\0');
2413 prrf = prrfRedrawOne | prrfUseSfbuf;
2414 if (cantent->form[hfn].flags & hffMethodPost) prrf |= prrfPost;
2415 wk_browser_prr(require_browser_window(), cantent->form[hfn].action_uri, prrf,
2416 document);
2417 }
2418
current_document_form_submit(void)2419 static void current_document_form_submit(void)
2420 { const tBrowserDocument* document = current_document_x;
2421 if (document != NULL) document_form_submit(document);
2422 }
2423
document_form_reset(tBrowserDocument * document)2424 static void document_form_reset(tBrowserDocument* document)
2425 { const tCantent* cantent = document->cantent;
2426 const tActiveElementBase* aebase;
2427 tActiveElementNumber _ae, a1, a2;
2428 tActiveElement* aes;
2429 #if TGC_IS_GRAPHICS
2430 tActiveElementNumber aecur = INVALID_AE; /* FIXME! */
2431 tHtmlFormNumber hfn = INVALID_HTML_FORM_NUMBER; /* FIXME! */
2432 #else
2433 tActiveElementNumber aecur = document->aecur;
2434 tHtmlFormNumber hfn = calc_hfn(cantent, aecur);
2435 #endif
2436 if ( (cantent == NULL) || (hfn == INVALID_HTML_FORM_NUMBER) )
2437 { cant_reset: show_message(_("Can't reset - not inside a form?"), truE);
2438 return;
2439 }
2440 a1 = cantent->form[hfn].first_ae; a2 = cantent->form[hfn].last_ae;
2441 if ( (a1 == INVALID_AE) || (a2 == INVALID_AE) ) goto cant_reset;
2442 javascript_handle_event(jekReset, aecur);
2443 aebase = cantent->aebase; aes = document->active_element;
2444 for (_ae = a1; _ae <= a2; _ae++)
2445 init_ae(&(aebase[_ae]), &(aes[_ae]), falsE, document);
2446 document_display(document, wrtRedraw);
2447 }
2448
current_document_form_reset(void)2449 static void current_document_form_reset(void)
2450 { tBrowserDocument* document = current_document_x;
2451 if (document != NULL) document_form_reset(document);
2452 }
2453
2454
2455 /* Browser documents: displaying on screen */
2456
2457 #if (TGC_IS_WINDOWING) || (CONFIG_EXTRA & EXTRA_DUMP) || (DO_PAGER)
2458 #include "renderer.c"
2459 #endif
2460
wrc_rd_setup(tRendererData * rd,tBrowserDocument * document,short width)2461 static void wrc_rd_setup(tRendererData* rd, /*@notnull@*/ tBrowserDocument*
2462 document, short width)
2463 { const tCantent* cantent = document->cantent;
2464 const tBrowserDocumentDisplayMode bddm = document->bddm;
2465 my_memclr(rd, sizeof(tRendererData));
2466 rd->ra = raLayout; rd->document = document; rd->line_width = width;
2467 rd->flags = rdfAttributes | rdfAlignment | rdfAe;
2468 #if MIGHT_USE_COLORS
2469 rd->flags |= rdfColors;
2470 #endif
2471 #if TGC_IS_GRAPHICS
2472 rd->flags |= rdfGraphical;
2473 #endif
2474 #if TGC_IS_PIXELING
2475 rd->flags |= rdfPixeling;
2476 #endif
2477 #if CONFIG_HTML & HTML_FRAMES
2478 if (!(config.flags & cfHtmlFramesSimple)) rd->flags |= rdfFrames;
2479 #endif
2480 if (bddm != bddmAutodetect) /* the user explicitly wants something */
2481 { if (bddm == bddmHtml) { set_html: rd->flags |= rdfHtml; }
2482 goto kind_done;
2483 }
2484 if ( (cantent != NULL) && (cantent->kind == rckHtml) )
2485 goto set_html; /* resource kind knowledge, no explicit user preference */
2486 kind_done: {}
2487 }
2488
2489 my_enum1 enum
2490 { wrfNone = 0, wrfFound = 0x01
2491 } my_enum2(unsigned char) tWindowRedrawingFlags; /* CHECKME: rename! */
2492
2493 typedef struct
2494 { tLinenumber origin_y, currline, highest_backward_line;
2495 size_t searchlen;
2496 short row, maxrow, mincol;
2497 tWindowRedrawingTask task;
2498 tWindowRedrawingFlags flags;
2499 } tWindowRedrawingData; /* CHECKME: rename! */
2500
__document_display_line(tWindowRedrawingData * wrd,tRendererData * data)2501 static void __document_display_line(tWindowRedrawingData* wrd,
2502 tRendererData* data)
2503 /* actually draws a line on the screen */
2504 { const tRendererElement* element = data->element;
2505 (void) move(wrd->row, wrd->mincol);
2506 while (element != NULL)
2507 { const tRendererText* text = element->text;
2508 const tRendererAttr* attr = element->attr;
2509 size_t count = 0, textcount = element->textcount;
2510 if (element->is_spacer) { while (count++ < textcount) addch(' '); }
2511 else
2512 { while (count < textcount)
2513 { tRendererAttr a = 0;
2514 if (text != NULL) a |= (tRendererAttr) text[count];
2515 if (attr != NULL) a |= attr[count];
2516 if (a == 0) /* "should not happen" */
2517 a = (tRendererAttr) ((unsigned char) ' ');
2518 addch(a); count++;
2519 }
2520 }
2521 element = element->next;
2522 }
2523 (void) clrtoeol(); wrd->row++;
2524 if (wrd->row > wrd->maxrow) data->flags |= rdfCallerDone;
2525 }
2526
document_display_line__standard(tRendererData * data)2527 static void document_display_line__standard(tRendererData* data)
2528 { tWindowRedrawingData* wrd = data->line_callback_data;
2529 if (wrd->currline < wrd->origin_y)
2530 { wrd->currline++;
2531 if (wrd->currline >= wrd->origin_y) data->flags &= ~rdfVirtual;
2532 }
2533 else { wrd->currline++; __document_display_line(wrd, data); }
2534 }
2535
2536 static /*@null@*/ const char* search_string = NULL;
2537
search_in_line(const tRendererElement * element,size_t searchlen)2538 static tBoolean search_in_line(const tRendererElement* element,
2539 size_t searchlen)
2540 /* returns whether it found something */
2541 { tBoolean retval = falsE;
2542 while (element != NULL)
2543 { const char* text;
2544 size_t textcount = element->textcount;
2545 if ( (!(element->is_spacer)) && ( (text = element->text) != NULL ) &&
2546 ( (textcount = element->textcount) >= searchlen ) &&
2547 (my_strncasestr(text, search_string, textcount)) )
2548 { retval = truE; break; }
2549 element = element->next;
2550 }
2551 return(retval);
2552 }
2553
document_display_line__search(tRendererData * data)2554 static void document_display_line__search(tRendererData* data)
2555 { tWindowRedrawingData* wrd = data->line_callback_data;
2556 if (wrd->currline <= wrd->origin_y)
2557 { wrd->currline++;
2558 if (wrd->currline > wrd->origin_y) data->flags &= ~rdfVirtual;
2559 }
2560 else if (wrd->flags & wrfFound)
2561 { wrd->currline++; do_redraw: __document_display_line(wrd, data); }
2562 else
2563 { wrd->currline++;
2564 if (search_in_line(data->element, wrd->searchlen))
2565 { if (data->document != NULL) data->document->origin_y = wrd->currline - 1;
2566 wrd->flags |= wrfFound; goto do_redraw;
2567 }
2568 }
2569 }
2570
document_display_line__search_backward(tRendererData * data)2571 static void document_display_line__search_backward(tRendererData* data)
2572 { tWindowRedrawingData* wrd = data->line_callback_data;
2573 wrd->currline++;
2574 if (wrd->currline < wrd->origin_y)
2575 { if (search_in_line(data->element, wrd->searchlen))
2576 { wrd->highest_backward_line = wrd->currline - 1; wrd->flags |= wrfFound; }
2577 }
2578 else data->flags |= rdfCallerDone;
2579 }
2580
document_display_line__count(tRendererData * data)2581 static void document_display_line__count(tRendererData* data)
2582 { tWindowRedrawingData* wrd = data->line_callback_data;
2583 wrd->currline++;
2584 }
2585
document_do_display(tBrowserDocument * document,const short mincol,const short minrow,const short maxcol,const short maxrow,const tWindowRedrawingTask task)2586 static void document_do_display(/*@notnull@*/ tBrowserDocument* document,
2587 const short mincol, const short minrow, const short maxcol,
2588 const short maxrow, const tWindowRedrawingTask task)
2589 { tRendererData data;
2590 tWindowRedrawingData wrd;
2591 const tBoolean is_search =
2592 cond2boolean( (task == wrtSearch) || (task == wrtSearchBackward) );
2593 #if !TGC_IS_GRAPHICS
2594 tActiveElementNumber _ae;
2595 #endif
2596
2597 if (task != wrtRedrawRecursive) { (document->ops->display_meta1)(document); }
2598 wrc_rd_setup(&data, document, maxcol - mincol + 1);
2599 my_memclr_var(wrd); data.line_callback_data = &wrd;
2600 data.line_callback = document_display_line__standard; /* default callback */
2601 if (is_search)
2602 { wrd.searchlen = strlen(search_string);
2603 if (task == wrtSearch) data.line_callback = document_display_line__search;
2604 else if (task == wrtSearchBackward)
2605 data.line_callback = document_display_line__search_backward;
2606 }
2607 else if (task == wrtToEnd) data.line_callback = document_display_line__count;
2608
2609 #if !TGC_IS_GRAPHICS
2610 _ae = document->aecur;
2611 if (_ae != INVALID_AE) do_activate_element(document, _ae, falsE);
2612 #endif
2613
2614 wrd.row = minrow; wrd.maxrow = maxrow; wrd.mincol = mincol; wrd.task = task;
2615 wrd.origin_y = document->origin_y;
2616 switch (task)
2617 { case wrtSearch: case wrtToEnd: data.flags |= rdfVirtual; break;
2618 case wrtSearchBackward: /* nothing */ break;
2619 default: if (wrd.origin_y > 0) { data.flags |= rdfVirtual; } break;
2620 }
2621
2622 renderer_run(&data);
2623
2624 if (task == wrtRedraw) { /* nothing (test most likely case first) */ }
2625 else if (is_search)
2626 { const tBoolean did_find = cond2boolean(wrd.flags & wrfFound);
2627 if ( (did_find) && (task == wrtSearchBackward) )
2628 document->origin_y = wrd.highest_backward_line;
2629 /* IMPROVEME: don't redraw always, only if necessary! */
2630 document_do_display(document, mincol, minrow, maxcol, maxrow,
2631 wrtRedrawRecursive); /* risk a recursion :-) */
2632 if (did_find) show_message(_("Found!"), falsE);
2633 else show_message(_("Not found!"), truE);
2634 goto out; /* rest was done in recursively called instance */
2635 }
2636 else if (task == wrtToEnd)
2637 { const short height = maxrow - minrow;
2638 const tLinenumber num = wrd.currline;
2639 document->origin_y = ( (num > height) ? (num - height) : 0 );
2640 document_do_display(document, mincol, minrow, maxcol, maxrow,
2641 wrtRedrawRecursive);
2642 goto out;
2643 }
2644 if (wrd.row > wrd.maxrow) document->flags |= wvfScreenFull;
2645 else document->flags &= ~wvfScreenFull;
2646 while (wrd.row <= wrd.maxrow)
2647 { (void) move(wrd.row, mincol); (void) clrtoeol(); wrd.row++; }
2648
2649 #if !TGC_IS_GRAPHICS
2650 if (_ae == INVALID_AE) _ae = document->former_ae;
2651 if (_ae != INVALID_AE)
2652 { if (!is_ae_visible(document, _ae))
2653 { if (document->flags & wvfScrollingUp)
2654 _ae = previous_visible_ae(document, _ae);
2655 else _ae = next_visible_ae(document, _ae);
2656 }
2657 }
2658 else
2659 { _ae = next_visible_ae(document, -1); /* find the first visible ae */
2660 if ( (_ae != INVALID_AE) && (document->flags & wvfScrollingUp) )
2661 { /* proceed to the _last_ visible active element */
2662 tActiveElementNumber _ae2, l = document->aenum;
2663 for (_ae2 = _ae + 1; _ae2 < l; _ae2++)
2664 { if (is_ae_visible(document, _ae2)) _ae = _ae2; }
2665 }
2666 }
2667 if (_ae != INVALID_AE) activate_element(document, _ae);
2668 #endif
2669 (document->ops->display_meta2)(document);
2670 document->flags &= ~wvfScrollingUp;
2671 #if 0
2672 /* some texts for the very next versions...
2673 _("looking for fragment"), _("fragment not found")
2674 _("disk cache file"), _("Do not edit manually while retawq is running!"),
2675 _("Upload local file: "), _("Upload to FTP URL: "),
2676 _("Resume upload? [(r)esume, (o)verwrite%s]"), _(", (s)kip"),
2677 _("File size: local %d, remote %d")
2678 */
2679 #endif
2680 out: {}
2681 }
2682
document_display(tBrowserDocument * document,const tWindowRedrawingTask task)2683 static void document_display(tBrowserDocument* document,
2684 const tWindowRedrawingTask task)
2685 { short mincol, minrow, maxcol, maxrow;
2686 if (document_find_coords(document, &mincol, &minrow, &maxcol, &maxrow))
2687 { document_do_display(document, mincol, minrow, maxcol, maxrow, task);
2688 must_reset_cursor();
2689 }
2690 /* "else": "should not happen" */
2691 }
2692
2693
2694 /** Downloads */
2695
2696 #if CONFIG_EXTRA & (EXTRA_DOWNLOAD | EXTRA_DUMP)
2697
print_automated_uri(const char * uri)2698 static void print_automated_uri(const char* uri)
2699 { if (lfdmbs(2))
2700 { my_write_str(fd_stderr, strUriColonSpace);
2701 /* (no _() here - this output shall be machine-parseable!) */
2702 my_write_str(fd_stderr, uri); my_write_str(fd_stderr, strNewline);
2703 }
2704 }
2705
2706 #endif
2707
2708 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
2709
2710 static const char strCantDownload[] = N_("can't download to file");
2711 static const tBrowserDocument* khmli_download_referrer = NULL;
2712 static tResourceRequest* pm_request; /* for pmDownload */
2713
rwd_cb_reqrem(const tRemainingWork * rw)2714 static tBoolean rwd_cb_reqrem(const tRemainingWork* rw)
2715 { tResourceRequest* request = (tResourceRequest*) (rw->data1);
2716 request_remove(request); return(falsE);
2717 }
2718
schedule_request_removal(tResourceRequest * request)2719 static void schedule_request_removal(tResourceRequest* request)
2720 { /* Sometimes we can't remove a request directly because then a dhm-related
2721 call chain could try to access deallocated memory on unwinding. Thus: */
2722 tRemainingWork* rw = remaining_work_create(rwd_cb_reqrem);
2723 rw->data1 = request;
2724 }
2725
download_request_callback(void * _request,tDhmNotificationFlags flags)2726 static void download_request_callback(void* _request,
2727 tDhmNotificationFlags flags)
2728 { tResourceRequest* request = (tResourceRequest*) _request;
2729 #if CONFIG_DEBUG
2730 sprint_safe(debugstrbuf,"download_request_callback(%p, %d)\n",request,flags);
2731 debugmsg(debugstrbuf);
2732 #endif
2733 if (flags & (dhmnfDataChange | dhmnfMetadataChange))
2734 { const tBoolean doing_auto = cond2boolean( (program_mode == pmDownload) &&
2735 (request == pm_request) );
2736 tResource* resource = request->resource;
2737 tResourceError re;
2738 if (resource != NULL)
2739 { re = resource->error;
2740 if (re != reFine)
2741 { if (doing_auto)
2742 { const char* text;
2743 #if OPTION_TLS
2744 if ( (re == reTls) && (tls_errtext(resource, strbuf)) ) text=strbuf;
2745 else
2746 #endif
2747 { handle_re: text = _(strResourceError[re]); }
2748 fatal_error(0, text);
2749 }
2750 req_rem: schedule_request_removal(request);
2751 }
2752 else if (resource->flags & rfFinal)
2753 { if (doing_auto) do_quit(); /* done */
2754 goto req_rem;
2755 }
2756 }
2757 else if ( (re = request->error) != reFine )
2758 { if (doing_auto) goto handle_re;
2759 else goto req_rem;
2760 }
2761 }
2762 else if (flags & dhmnfAttachery) /* a resource was attached to the request */
2763 { dhm_notification_setup(request->resource, download_request_callback,
2764 request, dhmnfDataChange | dhmnfMetadataChange, dhmnSet);
2765 }
2766 }
2767
download_queue(tResourceRequest * request,int fd)2768 static void download_queue(tResourceRequest* request, int fd)
2769 /* some common stuff for slightly different kinds of downloads */
2770 { request->flags |= rrfDownload; make_fd_cloexec(fd);
2771 sinking_data_mcleanup(); sinking_data.download_fd = fd;
2772 sinking_data.flags |= sdfIsDownloadFdValid;
2773 sinking_data_shift(&(request->sinking_data));
2774 request_queue(request, rraEnforcedReload);
2775 }
2776
content_download_to_fd(const char * uri,const tBrowserDocument * referrer,int fd)2777 static void content_download_to_fd(const char* uri,
2778 const tBrowserDocument* referrer, int fd)
2779 { tPrrData prr_data;
2780 tResourceRequest* request;
2781 prr_setup(&prr_data, uri, prrfNone); prr_data.referrer = referrer;
2782 prepare_resource_request(&prr_data); request = prr_data.result;
2783 if (program_mode == pmDownload)
2784 { pm_request = request; print_automated_uri(request->uri_data->uri); }
2785 if (request->state == rrsError)
2786 { inform_about_error(0, _(strResourceError[request->error]));
2787 my_close(fd); goto out;
2788 }
2789 prr_data.result = NULL;
2790 dhm_notification_setup(request, download_request_callback, request,
2791 dhmnfDataChange | dhmnfMetadataChange | dhmnfAttachery, dhmnSet);
2792 download_queue(request, fd);
2793 out:
2794 prr_setdown(&prr_data);
2795 }
2796
__content_download(const char * filename,tBoolean may_overwrite)2797 static void __content_download(const char* filename, tBoolean may_overwrite)
2798 { const char* uri = khmli_download_uri;
2799 const tBrowserDocument* referrer = khmli_download_referrer;
2800 int fd, cflags = O_CREAT | O_TRUNC | O_WRONLY;
2801 struct stat statbuf;
2802 khmli_download_referrer = NULL;
2803 if (!may_overwrite) cflags |= O_EXCL;
2804 fd = my_create(filename, cflags, S_IRUSR | S_IWUSR);
2805 if (fd < 0)
2806 { failed: inform_about_error(errno, _(strCantDownload)); return; }
2807 if (my_fstat(fd, &statbuf) != 0)
2808 { int e; close_bad: e = errno; my_close(fd); errno = e; goto failed; }
2809 if (!S_ISREG(statbuf.st_mode))
2810 { errno = ( (S_ISDIR(statbuf.st_mode)) ? EISDIR : 0 );
2811 goto close_bad;
2812 }
2813 content_download_to_fd(uri, referrer, fd);
2814 }
2815
content_download(tBoolean may_overwrite)2816 static __my_inline void content_download(tBoolean may_overwrite)
2817 { __content_download(khmli_filename, may_overwrite);
2818 }
2819
2820 #endif /* #if CONFIG_EXTRA & EXTRA_DOWNLOAD */
2821
2822
2823 /** Dumps */
2824
2825 #if CONFIG_EXTRA & EXTRA_DUMP
2826
2827 typedef struct
2828 { char* line;
2829 int fd;
2830 } tContentDumpData;
2831
content_dump_line(tRendererData * data)2832 static void content_dump_line(tRendererData* data)
2833 { const tContentDumpData* cdd = data->line_callback_data;
2834 const tRendererElement* element = data->element;
2835 char* line = cdd->line;
2836 size_t count = 0, cnt;
2837 while (element != NULL)
2838 { const size_t textcount = element->textcount;
2839 if (textcount > 0)
2840 { if (element->is_spacer)
2841 { for (cnt = 0; cnt < textcount; cnt++) line[count++] = ' '; }
2842 else
2843 { const tRendererText* text = element->text;
2844 for (cnt = 0; cnt < textcount; cnt++) line[count++] = text[cnt];
2845 }
2846 }
2847 element = element->next;
2848 }
2849 line[count++] = '\n';
2850 if (my_write(cdd->fd, line, count) != (ssize_t) count)
2851 { inform_about_error(errno, _("can't dump line"));
2852 data->flags |= rdfCallerDone;
2853 }
2854 }
2855
content_dump_to_fd(tBrowserDocument * document,int fd,const tDumpStyle * dump_style)2856 static void content_dump_to_fd(tBrowserDocument* document, int fd,
2857 const tDumpStyle* dump_style)
2858 { tRendererData data;
2859 tContentDumpData cdd;
2860 const size_t line_width = 80;
2861 const tDumpStyleFlags flags = ( (dump_style != NULL) ? (dump_style->flags) :
2862 dsfNone );
2863 my_memclr_var(data);
2864 data.line_callback = content_dump_line; data.line_callback_data = &cdd;
2865 data.ra = raLayout; data.document = document; data.line_width = line_width;
2866 data.flags = rdfAlignment;
2867 if (flags & dsfMustHtml) { do_html: data.flags |= rdfHtml; }
2868 else if (!(flags & dsfMustSource))
2869 { if (document->bddm == bddmHtml) goto do_html;
2870 if ( (document->cantent != NULL) && (document->cantent->kind == rckHtml) )
2871 goto do_html;
2872 }
2873 cdd.fd = fd; cdd.line = __memory_allocate(line_width + 5, mapString);
2874 renderer_run(&data); memory_deallocate(cdd.line);
2875 }
2876
content_dump(tBrowserDocument * document,const char * filename,tBoolean may_overwrite,const tDumpStyle * dump_style)2877 static void content_dump(tBrowserDocument* document, const char* filename,
2878 tBoolean may_overwrite, const tDumpStyle* dump_style)
2879 { int fd, cflags = O_CREAT | O_TRUNC | O_WRONLY;
2880 struct stat statbuf;
2881 if (!may_overwrite) cflags |= O_EXCL;
2882 fd = my_create(filename, cflags, S_IRUSR | S_IWUSR);
2883 if (fd < 0)
2884 { failed: inform_about_error(errno, _("can't dump to file")); return; }
2885 if (my_fstat(fd, &statbuf) != 0)
2886 { int e; close_bad: e = errno; my_close(fd); errno = e; goto failed; }
2887 if (!S_ISREG(statbuf.st_mode))
2888 { errno = ( (S_ISDIR(statbuf.st_mode)) ? EISDIR : 0 );
2889 goto close_bad;
2890 }
2891 content_dump_to_fd(document, fd, dump_style); my_close(fd);
2892 }
2893
dump_request_callback(void * _request,tDhmNotificationFlags flags)2894 static void dump_request_callback(void* _request, tDhmNotificationFlags flags)
2895 { tResourceRequest* request = (tResourceRequest*) _request;
2896 #if CONFIG_DEBUG
2897 sprint_safe(debugstrbuf, "dump_request_callback(%p, %d)\n", request, flags);
2898 debugmsg(debugstrbuf);
2899 #endif
2900 if (flags & (dhmnfDataChange | dhmnfMetadataChange))
2901 { tResource* resource = request->resource;
2902 tResourceError re;
2903 if (resource != NULL)
2904 { re = resource->error;
2905 if (re != reFine)
2906 { const char* text;
2907 #if OPTION_TLS
2908 if ( (re == reTls) && (tls_errtext(resource, strbuf)) ) text = strbuf;
2909 else
2910 #endif
2911 { handle_re: text = _(strResourceError[re]); }
2912 fatal_error(0, text);
2913 }
2914 else if (resource->flags & rfFinal)
2915 { tBrowserDocument document;
2916 tDumpStyle ds;
2917 my_memclr_var(document); document.cantent = resource->cantent;
2918 my_memclr_var(ds); content_dump_to_fd(&document, fd_stdout, &ds);
2919 do_quit(); /* done */
2920 }
2921 }
2922 else if ( (re = request->error) != reFine ) goto handle_re;
2923 }
2924 else if (flags & dhmnfAttachery) /* a resource was attached to the request */
2925 { dhm_notification_setup(request->resource, dump_request_callback, request,
2926 dhmnfDataChange | dhmnfMetadataChange, dhmnSet);
2927 }
2928 }
2929
content_dump_auto(const char * uri)2930 static one_caller void content_dump_auto(const char* uri)
2931 { tPrrData prr_data;
2932 tResourceRequest* request;
2933 prr_setup(&prr_data, uri, prrfNone); prepare_resource_request(&prr_data);
2934 request = prr_data.result; prr_data.result = NULL;
2935 print_automated_uri(request->uri_data->uri);
2936 if (request->state == rrsError)
2937 fatal_error(0, _(strResourceError[request->error]));
2938 dhm_notification_setup(request, dump_request_callback, request,
2939 dhmnfDataChange | dhmnfMetadataChange | dhmnfAttachery, dhmnSet);
2940 request_queue(request, rraEnforcedReload); prr_setdown(&prr_data);
2941 }
2942
2943 #endif /* #if CONFIG_EXTRA & EXTRA_DUMP */
2944
2945
2946 /** Sessions */
2947
2948 #if CONFIG_SESSIONS
2949
2950 static const char strSessionMarker[] = "|#|x:";
2951 #define smlp (3) /* session marker letter position */
2952 #define psm(buf, ch) /* prepare session marker */ \
2953 do { strcpy((buf), strSessionMarker); *((buf) + smlp) = (ch); } while (0)
2954 #define bdp(buf) ((buf) + strlen(strSessionMarker)) /* buffer data position */
2955
2956 /* CHECKME: maybe this shouldn't be global... */
2957 static char flagsbuf[50], linenumbuf[50], widthbuf[50], winbuf[50],
2958 htmltitlemarker[10], urimarker[10];
2959
session_save(const char * filename,tBoolean may_overwrite)2960 static void session_save(const char* filename, tBoolean may_overwrite)
2961 { int fd, cflags = O_CREAT | O_TRUNC | O_WRONLY;
2962 struct stat statbuf;
2963 tWindow* window;
2964
2965 /* create the file */
2966 if (!may_overwrite) cflags |= O_EXCL;
2967 fd = my_create(filename, cflags, S_IRUSR | S_IWUSR);
2968 if (fd < 0) { failed: show_message_osfailure(errno, NULL); return; }
2969 if (my_fstat(fd, &statbuf) != 0)
2970 { int e; close_bad: e = errno; my_close(fd); errno = e; goto failed; }
2971 if (!S_ISREG(statbuf.st_mode))
2972 { errno = ( (S_ISDIR(statbuf.st_mode)) ? EISDIR : 0 );
2973 goto close_bad;
2974 }
2975
2976 /* save the session */
2977 my_write_str(fd,
2978 _("# Session file for retawq (http://retawq.sourceforge.net/)\n"));
2979 psm(flagsbuf, 'f'); psm(linenumbuf, 'l'); psm(htmltitlemarker, 'h');
2980 psm(urimarker, 'u'); psm(widthbuf, 'w'); *(bdp(widthbuf)) = *strTG;
2981 winbuf[0] = 'W'; winbuf[1] = ':';
2982 window = windowlisthead;
2983 while (window != NULL)
2984 { void (*func)(tWindow*, int) = window->spec->save_session;
2985 if (func != NULL) (func)(window, fd);
2986 window = window->next;
2987 }
2988
2989 /* finish */
2990 my_close(fd);
2991 }
2992
2993 static tLinenumber session_linenum;
2994 static tWindow* session_window;
2995 #if TGC_IS_CURSES
2996 static tWindow* session_viswin[2];
2997 #endif
2998 #define SESSIONPART_MAXNUM (10)
2999 static const char* session_part[SESSIONPART_MAXNUM];
3000 static unsigned char session_numparts;
3001
session_resume_split(char * line)3002 static void session_resume_split(char* line)
3003 { char* temp;
3004 session_numparts = 0;
3005 loop:
3006 temp = my_strstr(line, "|#|");
3007 if (temp != NULL)
3008 { char ch = temp[3];
3009 if (!my_islower(ch)) { incandloop: line = temp + 1; goto loop; }
3010 if (temp[4] != ':') goto incandloop;
3011 *temp = '\0';
3012 session_part[session_numparts++] = temp + 3;
3013 if (session_numparts < SESSIONPART_MAXNUM)
3014 { line = temp + 5; goto loop; } /* look for more */
3015 }
3016 }
3017
session_resume_finish_window(void)3018 static __my_inline void session_resume_finish_window(void)
3019 {
3020 #if 0 /* FIXME! */
3021 if ( (session_window != NULL) && (session_currview != NULL) )
3022 session_window->current_view = session_currview;
3023 #endif
3024 }
3025
session_resume_window(void)3026 static void session_resume_window(void)
3027 { session_resume_finish_window();
3028 session_window = wk_browser_create();
3029 #if 0 /* FIXME! */
3030 session_currview = NULL;
3031 #endif
3032 }
3033
session_resume_line(const char * line,const char * limit)3034 static void session_resume_line(const char* line, const char* limit)
3035 { size_t len = limit - line;
3036 char buf[STRBUF_SIZE], *temp;
3037 unsigned char count;
3038 session_linenum++;
3039 if (len <= 0) return; /* empty line */
3040 if (len > STRBUF_SIZE - 5) return; /* very long line, can't be serious */
3041 my_memcpy(buf, line, len); buf[len] = '\0'; /* (for simplicity) */
3042 if (buf[len - 1] == '\r') /* (e.g. manually written file?) */
3043 buf[len - 1] = '\0';
3044 debugmsg("session_resume_line(): *"); debugmsg(buf); debugmsg("*\n");
3045 temp = buf;
3046 while ( (*temp == ' ') || (*temp == '\t') ) temp++; /* skip whitespace */
3047 if (*temp == '#') return; /* comment line */
3048 if (!strncmp(temp, "W:", 2))
3049 { session_resume_window();
3050 #if TGC_IS_CURSES
3051 session_resume_split(temp + 2);
3052 for (count = 0; count < session_numparts; count++)
3053 { const char *str = session_part[count], *data = str + 2;
3054 int num;
3055 debugmsg("session part W: *"); debugmsg(str); debugmsg("*\n");
3056 if (*str != 'v') continue; /* unknown part */
3057 if (*data == '\0') continue; /* empty part */
3058 my_atoi(data, &num, &data, 9);
3059 if ( (num >= 0) && (num <= 1) && (*data == '\0') )
3060 session_viswin[num] = session_window;
3061 }
3062 #endif
3063 }
3064 else if (!strncmp(temp, "U:", 2))
3065 { const char *uri, *flagstr, *html_title;
3066 session_resume_split(temp + 2);
3067 uri = flagstr = html_title = NULL;
3068 for (count = 0; count < session_numparts; count++)
3069 { const char *str = session_part[count], *data = str + 2;
3070 debugmsg("session part U: *"); debugmsg(str); debugmsg("*\n");
3071 if (*data == '\0') continue; /* empty part */
3072 switch (*str)
3073 { case 'u': uri = data; break;
3074 case 'f': flagstr = data; break;
3075 case 'h': html_title = data; break;
3076 /* "default": unknown part; IMPLEMENTME: print warning/error? */
3077 }
3078 }
3079 if (uri != NULL) /* got useful information */
3080 { /* tResourceRequest* request; */
3081 tPrrFlags prrf = prrfRedrawOne;
3082 /* tBoolean is_currview = falsE; */
3083 if (flagstr != NULL)
3084 { const char* f = flagstr;
3085 char ch;
3086 while ( (ch = *f++) != '\0' )
3087 { if (ch == 's') prrf |= prrfSource;
3088 else if (ch == 'h') prrf |= prrfHtml;
3089 /* else if (ch == 'c') is_currview = truE; */
3090 }
3091 }
3092 if (session_window == NULL) session_resume_window();
3093 wk_browser_prr(session_window, uri, prrf, NULL);
3094 #if 0 /* FIXME! */
3095 if (request != NULL)
3096 { if (is_currview) session_currview = view;
3097 if (html_title != NULL)
3098 my_strdedup(document->minor_html_title, html_title);
3099 }
3100 #endif
3101 }
3102 }
3103 /* "else": IMPLEMENTME: bad line, print a message, stop resumption! */
3104 }
3105
session_resume(const char * filename)3106 static void session_resume(const char* filename)
3107 { size_t size;
3108 void* filebuf;
3109 char *buf, *end, *temp;
3110 if (*filename == '\0') filename = config.session_default;
3111 debugmsg("session_resume(): *"); debugmsg(filename); debugmsg("*\n");
3112 switch (my_mmap_file_readonly(filename, &filebuf, &size))
3113 { case 0: show_message_osfailure(errno,NULL); return; /*@notreached@*/ break;
3114 case 1: goto out; /*@notreached@*/ break;
3115 }
3116 buf = (char*) filebuf; end = buf + size - 1;
3117 session_window = NULL;
3118 #if TGC_IS_CURSES
3119 session_viswin[0] = session_viswin[1] = NULL;
3120 #endif
3121 #if 0 /* FIXME! */
3122 session_currview = NULL;
3123 #endif
3124 session_linenum = 0;
3125 temp = buf;
3126 while (temp <= end)
3127 { if (*temp == '\n') { session_resume_line(buf, temp); buf = temp + 1; }
3128 temp++;
3129 }
3130 my_munmap(filebuf, size);
3131 session_resume_finish_window();
3132 #if TGC_IS_CURSES
3133 { tWindow *viswin0 = session_viswin[0], *viswin1 = session_viswin[1];
3134 if ( (viswin0 == NULL) && (viswin1 != NULL) )
3135 { viswin0 = viswin1; viswin1 = NULL; }
3136 if (viswin0 != NULL) visible_window_x[0] = viswin0;
3137 if ( (viswin1 != NULL) && (viswin1 != visible_window_x[0]) )
3138 visible_window_x[1] = viswin1;
3139 }
3140 #endif
3141 window_redraw_all();
3142
3143 out:
3144 show_message(_(strDone), falsE);
3145 }
3146
3147 #endif /* #if CONFIG_SESSIONS */
3148
3149
3150 /** Contextual menus (that's a misnomer nowadays; should be something like
3151 "general/basic menu handling", but I don't want to change the nice "cm_"
3152 prefix all over the place) */
3153
3154 #if CONFIG_MENUS
3155
3156 #if TGC_IS_CURSES
3157
3158 #if CONFIG_MENUS & MENUS_BAR
3159 static tBoolean doing_mbar = falsE;
3160 #endif
3161
my_hline(short x1,short x2,short y)3162 static void my_hline(short x1, short x2, short y)
3163 /* draws a horizontal line */
3164 { (void) move(y, x1);
3165 while (x1 <= x2) { (void) addch(__MY_HLINE); x1++; }
3166 }
3167
my_vline(short y1,short y2,short x)3168 static void my_vline(short y1, short y2, short x)
3169 /* draws a vertical line */
3170 { while (y1 <= y2) { (void) mvaddch(y1, x, __MY_VLINE); y1++; }
3171 }
3172
draw_box(short x1,short y1,short x2,short y2)3173 static void draw_box(short x1, short y1, short x2, short y2)
3174 { my_set_color(cpnBlue); /* attron(A_BOLD); */
3175 my_hline(x1 + 1, x2 - 1, y1); my_hline(x1 + 1, x2 - 1, y2);
3176 my_vline(y1 + 1, y2 - 1, x1); my_vline(y1 + 1, y2 - 1, x2);
3177 (void) mvaddch(y1, x1, __MY_UL); (void) mvaddch(y1, x2, __MY_UR);
3178 (void) mvaddch(y2, x1, __MY_LL); (void) mvaddch(y2, x2, __MY_LR);
3179 /* attroff(A_BOLD); */ my_set_color(cpnDefault);
3180 }
3181
3182 typedef void* tCmCallbackData;
3183 typedef void (*tCmCallbackFunction)(tCmCallbackData);
3184 typedef signed int tCmNumber; /* ("signed" for simplicity only) */
3185
3186 typedef struct
3187 { const char* render;
3188 tCmCallbackFunction function;
3189 tCmCallbackData data;
3190 } tCmEntry;
3191
3192 static tCmEntry* cm_info;
3193 static tCmNumber cm_num, cm_maxnum, cm_current,
3194 cm_topmost, cm_onscreen_num; /* what's displayed on the screen */
3195 static tCoordinate cm_x1, cm_y1, cm_x2, cm_y2;
3196
3197 static char* cm_select_bitfield;
3198 static tBoolean cm_select_is_multiple;
3199 static tHtmlOptionNumber cm_select_old; /* formerly selected option */
3200
cm_init(void)3201 static void cm_init(void)
3202 { cm_info = NULL;
3203 cm_num = cm_maxnum = cm_current = cm_topmost = 0;
3204 }
3205
__cm_add(const char * str,tCmCallbackFunction func,tCmCallbackData data)3206 static void __cm_add(const char* str, tCmCallbackFunction func,
3207 tCmCallbackData data)
3208 { if (cm_num >= cm_maxnum)
3209 { cm_maxnum += 20;
3210 cm_info = memory_reallocate(cm_info, cm_maxnum * sizeof(tCmEntry),
3211 mapOther);
3212 }
3213 cm_info[cm_num].render = my_strdup(str);
3214 cm_info[cm_num].function = func; cm_info[cm_num].data = data;
3215 cm_num++;
3216 }
3217
3218 #define cm_add(str, func, val) \
3219 __cm_add(unconstify_or_(str), func, (tCmCallbackData) MY_INT_TO_POINTER(val))
3220
cm_add_separator(void)3221 static my_inline void cm_add_separator(void)
3222 { if (cm_num > 0) __cm_add(strMinus, NULL, NULL);
3223 }
3224
cm_activate(tBoolean active)3225 static void cm_activate(tBoolean active)
3226 /* indicates on screen what's currently selected ("menu cursor") */
3227 { short x, x0 = cm_x1 + 1, y = (cm_y1 + 1) + (cm_current - cm_topmost);
3228 (void) move(y, x0);
3229 for (x = x0; x < cm_x2; x++)
3230 { chtype ch = inch();
3231 if (active) ch |= A_REVERSE;
3232 else ch &= ~A_REVERSE;
3233 (void) addch(ch);
3234 }
3235 (void) move(LINES - 1, 0);
3236 }
3237
__cm_draw(void)3238 static void __cm_draw(void)
3239 /* draws the menu texts */
3240 { tCmNumber num;
3241 short x = cm_x1 + 1, width = cm_x2 - cm_x1 - 1;
3242 for (num = 0; num < cm_onscreen_num; num++)
3243 { short y = cm_y1 + num + 1;
3244 const char* str = cm_info[cm_topmost + num].render;
3245 if ( (*str == '-') && (str[1] == '\0') ) my_hline(x, x + width - 1, y);
3246 else
3247 { short count = width - strlen(str);
3248 /*(void)*/ mvaddnstr(y, x, str, width);
3249 while (count-- > 0) (void) addch(' ');
3250 }
3251 }
3252 }
3253
cm_draw(short x,short y,const char * title)3254 static one_caller tBoolean cm_draw(short x, short y, const char* title)
3255 /* calculates and draws the menu rectangle; returns whether it worked */
3256 { short titlelen = ( (title != NULL) ? strlen(title) : 0 ),
3257 titlewidth = ( (titlelen > 0) ? (titlelen + 2) : 0 ),
3258 width = titlewidth, cols = COLS, lines = LINES, maxwidth = cols - 10;
3259 tCmNumber num, onscreen_maxnum = lines - 4;
3260 if ( (maxwidth <= 5) || (onscreen_maxnum <= 2) ) return(falsE);
3261 for (num = 0; num < cm_num; num++)
3262 { short len = strlen(cm_info[num].render);
3263 if (width < len) width = len;
3264 }
3265 if (width > maxwidth) width = maxwidth;
3266 cm_onscreen_num = MIN(cm_num, onscreen_maxnum);
3267
3268 if ( (x < 0) || (x >= cols) ) x = 0;
3269 if ( (y < 0) || (y >= lines) ) y = 0;
3270
3271 if (x + width + 1 >= cols - 1) x = cols - width - 3;
3272 if (y + cm_onscreen_num + 1 >= lines - 2) y = lines - cm_onscreen_num - 4;
3273
3274 cm_x1 = x; cm_y1 = y; cm_x2 = x + width + 1; cm_y2 = y + cm_onscreen_num + 1;
3275 draw_box(cm_x1, cm_y1, cm_x2, cm_y2);
3276 if (titlelen > 0)
3277 { short maxlen = cm_x2 - cm_x1 - 3, len = MIN(maxlen, titlelen),
3278 offset = (maxlen - len) / 2;
3279 if (offset < 0) offset = 0;
3280 my_set_color(cpnBlue);
3281 /*(void)*/ mvaddnstr(cm_y1, cm_x1 + 2 + offset, title, len);
3282 my_set_color(cpnDefault);
3283 }
3284 __cm_draw(); cm_activate(truE);
3285 return(truE);
3286 }
3287
cm_start(short x,short y,const char * title,const char * errstr)3288 static void cm_start(short x, short y, const char* title, const char* errstr)
3289 { if (cm_num > 0)
3290 { if (cm_draw(x, y, title)) key_handling_mode = khmMenu;
3291 else terminal_too_small();
3292 }
3293 else
3294 { /* The menu would be empty, so we won't show it. */
3295 if (errstr != NULL) show_message(errstr, truE);
3296 }
3297 }
3298
cm_handle_command_code(tCmCallbackData data)3299 static void cm_handle_command_code(tCmCallbackData data)
3300 { (void) handle_command_code((tProgramCommandCode) MY_POINTER_TO_INT(data));
3301 }
3302
3303 #if CONFIG_MENUS & MENUS_CONTEXT
3304
cm_setup_contextual(short x,short y,tActiveElementNumber _ae)3305 static void cm_setup_contextual(short x, short y, tActiveElementNumber _ae)
3306 /* sets up a contextual menu with ae-related entries (unless <_ae> is invalid)
3307 and some general entries */
3308 { tWindow* window = current_window_x; /* FIXME! */
3309 void (*func)(tWindow*, short*, short*, tActiveElementNumber) =
3310 window->spec->setup_cm;
3311 cm_init();
3312 if (func != NULL) (func)(window, &x, &y, _ae);
3313 cm_add(strUcClose, cm_handle_command_code, pccWindowClose);
3314 cm_add(strUcQuit, cm_handle_command_code, pccQuit);
3315 cm_start(x, y, _(strContext), NULL);
3316 }
3317
3318 #endif /* #if CONFIG_MENUS & MENUS_CONTEXT */
3319
3320 #if CONFIG_MENUS & MENUS_HTML
3321
cm_handle_select_tag(tCmCallbackData data)3322 static void cm_handle_select_tag(tCmCallbackData data)
3323 /* callback function for menus for the HTML <select> tag */
3324 { tHtmlOptionNumber num = (tHtmlOptionNumber) MY_POINTER_TO_INT(data);
3325 if (cm_select_is_multiple) my_bit_flip(cm_select_bitfield, num);
3326 else
3327 { my_bit_clear(cm_select_bitfield, cm_select_old);
3328 my_bit_set(cm_select_bitfield, num);
3329 }
3330 window_redraw(current_window_x);
3331 }
3332
cm_setup_select_tag(const tActiveElementBase * aebase,const tBrowserDocument * document,tActiveElementNumber _ae)3333 static void cm_setup_select_tag(const tActiveElementBase* aebase,
3334 const tBrowserDocument* document, tActiveElementNumber _ae)
3335 /* sets up a menu for the HTML <select> tag */
3336 { tActiveElement* aes = document->active_element;
3337 const tHtmlOption* o = (const tHtmlOption*) (aebase[_ae].render);
3338 const tActiveElementCoordinates* aec = find_visible_aec(document, _ae);
3339 tHtmlOptionNumber num = 0;
3340 short x, y;
3341 cm_init();
3342 cm_select_is_multiple = cond2boolean(aebase[_ae].flags & aefMultiple);
3343 cm_select_bitfield = aes[_ae].current_text;
3344 cm_select_old = 0;
3345 while (o != NULL)
3346 { tBoolean is_selected = cond2boolean(my_bit_test(cm_select_bitfield, num));
3347 char ch, *spfbuf;
3348 if (is_selected) ch = '+';
3349 else if (o->flags & hofDisabled) ch = '-'; /* CHECKME! */
3350 else ch = ' ';
3351 my_spf(strbuf, STRBUF_SIZE, &spfbuf, "%c%s", ch, o->render);
3352 __cm_add(spfbuf, cm_handle_select_tag,
3353 (tCmCallbackData) MY_INT_TO_POINTER(num));
3354 my_spf_cleanup(strbuf, spfbuf);
3355 if (is_selected) cm_select_old = num;
3356 o = o->next; num++;
3357 }
3358 if ( (aec != NULL) && (document_line2row(document, aec->y, &y)) )
3359 { x = aec->x1; y++; }
3360 else { x = y = 0; }
3361 cm_start(x, y, _("Options"), _(strSelectionEmpty));
3362 }
3363
3364 #endif /* #if CONFIG_MENUS & MENUS_HTML */
3365
3366 #if CONFIG_MENUS & MENUS_UHIST
3367
cm_handle_uri_history(tCmCallbackData data)3368 static void cm_handle_uri_history(tCmCallbackData data)
3369 { tWindow* window = require_browser_window();
3370 unsigned short i = (unsigned short) MY_POINTER_TO_INT(data);
3371 wk_browser_prr(window, uri_history[i], prrfRedrawOne, NULL);
3372 }
3373
cm_setup_uri_history(void)3374 static void cm_setup_uri_history(void)
3375 { unsigned short count = 0, i = uri_history_index;
3376 cm_init();
3377 while (count < URI_HISTORY_LEN)
3378 { const char* text;
3379 if (i > 0) i--;
3380 else i = URI_HISTORY_LEN - 1;
3381 text = uri_history[i];
3382 if (text == NULL) break; /* reached the "end" of the list */
3383 __cm_add(text, cm_handle_uri_history,
3384 (tCmCallbackData) MY_INT_TO_POINTER(i));
3385 count++;
3386 }
3387 cm_start(0, 0, _("History"), _("No URL history available yet!"));
3388 }
3389
3390 #endif /* #if CONFIG_MENUS & MENUS_UHIST */
3391
3392 #if CONFIG_MENUS & MENUS_WLIST
3393
cm_handle_window_list(tCmCallbackData data)3394 static void cm_handle_window_list(tCmCallbackData data)
3395 { tWindow* window = (tWindow*) data;
3396 if (current_window_x != window) /* CHECKME! */
3397 { current_window_x = window;
3398 window_redraw(current_window_x);
3399 }
3400 }
3401
cm_setup_window_list(void)3402 static void cm_setup_window_list(void)
3403 { tWindow* window = windowlisthead;
3404 cm_init();
3405 while (window != NULL)
3406 { const char* (*func)(tWindow*) = window->spec->get_menu_entry;
3407 const char* text = ( (func != NULL) ? ((func)(window)) : NULL );
3408 if (text != NULL)
3409 __cm_add(text, cm_handle_window_list, (tCmCallbackData) window);
3410 window = window->next;
3411 }
3412 cm_start(0, 0, _("Window List"), _("No open window!"));
3413 }
3414
3415 #endif /* #if CONFIG_MENUS & MENUS_WLIST */
3416
cm_remove(void)3417 static void cm_remove(void)
3418 /* This is only for "internal" use by cm_....() functions; external users
3419 should call cm_cancel() instead. */
3420 { tCmNumber num;
3421 for (num = 0; num < cm_num; num++) __dealloc(cm_info[num].render);
3422 memory_deallocate(cm_info);
3423 #if CONFIG_MENUS & MENUS_BAR
3424 doing_mbar = falsE;
3425 #endif
3426 key_handling_mode = khmCommand; window_redraw_all();
3427 }
3428
cm_change_current(tCmNumber num)3429 static void cm_change_current(tCmNumber num)
3430 /* moves the "menu cursor" */
3431 { tCmNumber old_topmost;
3432 if (cm_current == num) return; /* no "real" change, nothing to do */
3433 cm_activate(falsE); /* deactivate old */
3434 cm_current = num;
3435
3436 old_topmost = cm_topmost;
3437 if (cm_topmost > num) cm_topmost = num;
3438 else if (cm_topmost <= num - cm_onscreen_num)
3439 cm_topmost = num - cm_onscreen_num + 1;
3440 if (cm_topmost != old_topmost) __cm_draw(); /* need to "scroll" */
3441 cm_activate(truE);
3442 }
3443
cm_line_up(void)3444 static void cm_line_up(void)
3445 { if (cm_current > 0) cm_change_current(cm_current - 1);
3446 else if (cm_num > 0) cm_change_current(cm_num - 1);
3447 }
3448
cm_line_down(void)3449 static void cm_line_down(void)
3450 { if (cm_current < cm_num - 1) cm_change_current(cm_current + 1);
3451 else cm_change_current(0);
3452 }
3453
cm_page_up(void)3454 static void cm_page_up(void)
3455 { tCmNumber num = cm_current - (cm_y2 - cm_y1 - 1);
3456 if (num < 0) num = 0;
3457 cm_change_current(num);
3458 }
3459
cm_page_down(void)3460 static void cm_page_down(void)
3461 { tCmNumber num = cm_current + (cm_y2 - cm_y1 - 1);
3462 if (num > cm_num - 1) num = cm_num - 1;
3463 cm_change_current(num);
3464 }
3465
cm_cancel(void)3466 static __my_inline void cm_cancel(void)
3467 /* The user cancelled the menu explicitly or resized the terminal. */
3468 { cm_remove();
3469 }
3470
cm_apply(void)3471 static void cm_apply(void)
3472 /* The user pressed some KEY_ENTER variant. */
3473 { tCmCallbackFunction function = cm_info[cm_current].function;
3474 if (function != NULL)
3475 { tCmCallbackData data = cm_info[cm_current].data;
3476 cm_remove();
3477 (function)(data);
3478 }
3479 }
3480
3481 #if CONFIG_DO_TEXTMODEMOUSE
cm_handle_mouseclick(tCoordinate x,tCoordinate y)3482 static void cm_handle_mouseclick(tCoordinate x, tCoordinate y)
3483 { if ( (x > cm_x1) && (x < cm_x2) && (y > cm_y1) && (y < cm_y2) )
3484 { /* The user clicked inside the menu box. */
3485 tCmNumber num = ((tCmNumber) (y - cm_y1 - 1)) + cm_topmost;
3486 cm_change_current(num);
3487 cm_apply();
3488 }
3489 else cm_remove();
3490 }
3491 #endif
3492
3493 #endif /* #if TGC_IS_CURSES */
3494
3495 #endif /* #if CONFIG_MENUS */
3496
3497
3498 /** Line input II */
3499
3500 #if TGC_IS_GRAPHICS
3501
current_window_exists(void)3502 static tBoolean current_window_exists(void)
3503 /* returns whether current_window still exists; in graphics mode, it's possible
3504 that a user starts a dialog which is related to a document window, then
3505 closes the window and finally clicks the "action" button of the dialog, so
3506 the callback function would refer to a non-existing window. This problem
3507 could be solved by using _modal_ dialogs, but I hate modality because it
3508 takes away flexibility from users - often unnecessarily. */
3509 { if (current_window != NULL)
3510 { const tWindow* w = windowlisthead;
3511 while (w != NULL)
3512 { if (w == current_window) return(truE);
3513 w = w->next;
3514 }
3515 }
3516 return(falsE);
3517 }
3518
3519 /* "require current_window" */
3520 #define __rcw(solution) if (!current_window_exists()) solution;
3521 #define rcw __rcw(return) /* the simple solution */
3522 #define rcwc __rcw(current_window = wk_browser_create()) /* sometimes nicer */
3523
3524 #else
3525
3526 #define __rcw(solution) /* nothing; current_window _is_ valid */
3527 #define rcw
3528 #define rcwc
3529
3530 #endif
3531
3532 #if TGC_IS_GRAPHICS
3533
3534 #if 0
3535 typedef struct
3536 { tWindow* window;
3537 GtkEntry* entry;
3538 tEditableLineInputGoal elig;
3539 } tLigData;
3540
3541 static one_caller __sallocator tLigData* __callocator
3542 lig_data_create(tWindow* window, GtkWidget* _entry,
3543 tEditableLineInputGoal elig)
3544 { tLigData* retval = (tLigData*) __memory_allocate(sizeof(tLigData),
3545 mapLineInput);
3546 GtkEntry* entry = ( (_entry != NULL) ? GTK_ENTRY(_entry) : NULL );
3547 retval->window = window; retval->entry = entry; retval->elig = elig;
3548 return(retval);
3549 }
3550
3551 static void graphics_handle_lig_action(tGraphicsWidget* w __cunused,
3552 gpointer _data)
3553 { const tLigData* data = (tLigData*) _data;
3554 tEditableLineInputGoal elig = data->elig;
3555 current_window = data->window;
3556 if (is_confirmation_lig(_lig)) handle_lig_confirmation(_lig);
3557 else
3558 { GtkEntry* entry = data->entry;
3559 const char* text = ( (entry != NULL) ? gtk_entry_get_text(entry) : NULL );
3560 handle_lig_other(_lig, text);
3561 }
3562 current_window = NULL;
3563 }
3564 #endif
3565
graphics_window_destroy(tGraphicsWidget * w __cunused,gpointer data)3566 static void graphics_window_destroy(tGraphicsWidget* w __cunused,
3567 gpointer data)
3568 /* low-level callback for window destruction triggered by buttons; for
3569 _document_ windows, use window_hide()/_remove() instead! */
3570 { gtk_widget_destroy((tGraphicsWidget*) data); /* (yes!) */
3571 }
3572
dialog_create_button(GtkDialog * dialog,const char * text)3573 static GtkWidget* dialog_create_button(GtkDialog* dialog, const char* text)
3574 { GtkWidget* button = gtk_button_new_with_label(text);
3575 pack_box(dialog->action_area, button);
3576 show_widget(button);
3577 return(button);
3578 }
3579
3580 #define connect_button(b, h, d) connect_widget(b, strGtkClicked, h, d)
3581
3582 #if 0
3583 static void start_line_input_FAIL(tLineInputGoal _lig, const char* msg,
3584 const char* initstr, const char* wtitle, const char* wact,
3585 const char* wcancel)
3586 { GtkDialog* dialog = GTK_DIALOG(gtk_dialog_new());
3587 GtkWidget *button, *inputbox;
3588 if (wtitle != NULL) gtk_window_set_title(GTK_WINDOW(dialog), wtitle);
3589 if (is_confirmation_lig(_lig))
3590 { GtkWidget* label = gtk_label_new(msg);
3591 show_widget(label); pack_box(dialog->vbox, label);
3592 inputbox = NULL;
3593 }
3594 else
3595 { GtkWidget *hbox = gtk_hbox_new(FALSE, 0), *label = gtk_label_new(msg);
3596 inputbox = gtk_entry_new();
3597 if (initstr != NULL) gtk_entry_set_text(inputbox, (gchar*) initstr);
3598 show_widget(hbox); show_widget(label); show_widget(inputbox);
3599 pack_box(hbox, label); pack_box(hbox, inputbox);
3600 pack_box(dialog->vbox, hbox);
3601 }
3602
3603 button = dialog_create_button(dialog, wact);
3604 connect_button(button, graphics_handle_lig_action,
3605 lig_data_create(current_window, inputbox, _lig));
3606 connect_button(button, graphics_window_destroy, dialog);
3607
3608 button = dialog_create_button(dialog, wcancel);
3609 connect_button(button, graphics_window_destroy, dialog);
3610
3611 show_widget(dialog);
3612 }
3613 #endif
3614
__line_input_start(const char * msg,const char * initstr,tLineInputCallback func,void * data,tLineInputAreaFlags liaf)3615 static void __line_input_start(/*@notnull@*/ const char* msg,
3616 const char* initstr, tLineInputCallback func, void* data,
3617 tLineInputAreaFlags liaf)
3618 { /* FIXME! */
3619 }
3620
3621 typedef struct
3622 { GtkFileSelection* sel;
3623 tWindow* window;
3624 } tFileselData;
3625
3626 static one_caller __sallocator tFileselData* __callocator
filesel_data_create(GtkWidget * sel,tWindow * window)3627 filesel_data_create(GtkWidget* sel, tWindow* window)
3628 { tFileselData* retval = (tFileselData*) __memory_allocate(sizeof(*retval),
3629 mapGtk);
3630 retval->sel = GTK_FILE_SELECTION(sel); retval->window = window;
3631 return(retval);
3632 }
3633
filesel_handle(GtkFileSelection * widget __cunused,gpointer _data)3634 static void filesel_handle(GtkFileSelection* widget __cunused, gpointer _data)
3635 { const tFileselData* data = (tFileselData*) _data;
3636 const char* file = gtk_file_selection_get_filename(data->sel);
3637 if ( (file != NULL) && (*file != '\0') )
3638 prepare_resource_request(data->window, file, prrfRedrawOne, NULL, NULL);
3639 }
3640
3641 #else /* #if TGC_IS_GRAPHICS */
3642
line_input_redraw(void)3643 static void line_input_redraw(void)
3644 { tLineInputAreaIndex count;
3645 for (count = 0; count < lid.num_areas; count++)
3646 { tLineInputArea* lia = &(lid.area[count]);
3647 const tLineInputAreaFlags liaf = lia->flags;
3648 const short len = lia->len, first = lia->first, usable = lia->usable,
3649 row = lia->row, colmin = lia->colmin;
3650 short cnt = len - first;
3651 const char* src = lid.area[count].text + first;
3652 if (cnt > usable) cnt = usable;
3653 (void) move(row, colmin);
3654 if (cnt > 0)
3655 { if (liaf & liafDisguising) { while (cnt-- > 0) (void) addch('*'); }
3656 else (void) addnstr(src, cnt);
3657 }
3658 (void) clrtoeol(); /* IMPROVEME! */
3659 }
3660 (void) move(lid_area(row), lid_area(colcurr));
3661 }
3662
line_input_layout(void)3663 static void line_input_layout(void)
3664 { tLineInputAreaIndex numa = lid.num_areas, count;
3665 const short line = LINES - 1, usable = COLS - 1;
3666 short col = 0;
3667 for (count = 0; count < numa; count++)
3668 { lid.area[count].row = line;
3669 if (lid.area[count].flags & liafEditable)
3670 lid.area[count].flags |= liafFocusable;
3671 }
3672
3673 if (usable < 5 * numa)
3674 { key_handling_mode = khmCommand; terminal_too_small();
3675 (lid.callback)(lid.callback_data, liekFail, 0);
3676 return;
3677 }
3678
3679 for (count = 0; count < numa; count++)
3680 { short len = lid.area[count].len, maxlen = usable - col - 5*(numa-count-1),
3681 used;
3682 if (len > maxlen) len = maxlen;
3683 used = ((lid.area[count].flags & liafEditable) ? maxlen : len);
3684 lid.area[count].colmin = lid.area[count].colcurr = col;
3685 lid.area[count].colmax = col + used; lid.area[count].usable = used;
3686 col += used;
3687 }
3688
3689 line_input_redraw();
3690 }
3691
line_input_resize(void)3692 static void line_input_resize(void)
3693 { short count;
3694 for (count = 0; count < lid.num_areas; count++)
3695 { lid.area[count].pos = 0; lid.area[count].first = 0; } /* KISS */
3696 line_input_layout();
3697 }
3698
__line_input_to_eos(void)3699 static one_caller void __line_input_to_eos(void)
3700 /* ("eos": "end of string", as usual) */
3701 { if (lid_area(len) >= lid_area(usable))
3702 { lid_area(first) = lid_area(len) - lid_area(usable);
3703 lid_area(colcurr) = lid_area(colmax);
3704 }
3705 else
3706 { lid_area(first) = 0;
3707 lid_area(colcurr) = lid_area(colmin) + lid_area(len);
3708 }
3709 }
3710
line_input_to_eos(void)3711 static void line_input_to_eos(void)
3712 { if (lid_area(pos) < lid_area(len))
3713 { lid_area(pos) = lid_area(len); __line_input_to_eos(); line_input_redraw();}
3714 }
3715
do_lookup_lineinput_key(tKey key)3716 static one_caller tMbsIndex do_lookup_lineinput_key(tKey key)
3717 { if (keymap_lineinput_keys_num <= 0) return(INVALID_INDEX); /*"can't happen"*/
3718 my_binary_search(0, keymap_lineinput_keys_num - 1,
3719 my_numcmp(key, keymap_lineinput_keys[idx].key), return(idx))
3720 }
3721
lookup_lineinput_key(tKey key)3722 static tLineInputActionCode lookup_lineinput_key(tKey key)
3723 { tMbsIndex idx = do_lookup_lineinput_key(key);
3724 return( (idx < 0) ? liacUnknown : keymap_lineinput_keys[idx].liac );
3725 }
3726
3727 #if CONFIG_DO_TEXTMODEMOUSE
mouse_flip(unsigned char what)3728 static void mouse_flip(unsigned char what)
3729 { static tBoolean is_on = cond2boolean(!OFWAX);
3730 tBoolean new_state;
3731 switch (what)
3732 { default: /* "can't happen" */ /*@fallthrough@*/
3733 case 0: new_state = cond2boolean(!is_on); break;
3734 case 1: new_state = falsE; break;
3735 case 2: new_state = truE; break;
3736 }
3737 if (is_on != new_state) /* must do something */
3738 { is_on = new_state;
3739 mousemask(is_on ? TEXTMODEMOUSE_MASK : 0, NULL);
3740 show_message(is_on ? _("mouse on") : _("mouse off"), falsE);
3741 }
3742 }
3743 #else
3744 static const char strFtmm[] = N_("Ftext-mode mouse");
3745 #define mouse_flip(dummy) fwdact(_(strFtmm))
3746 #endif
3747
line_input_handle_key(tKey key)3748 static one_caller void line_input_handle_key(tKey key)
3749 { tLineInputActionCode liac = lookup_lineinput_key(key);
3750 switch (liac)
3751 { case liacCancel:
3752 (lid.callback)(lid.callback_data, liekCancel, 0);
3753 return; /*@notreached@*/ break;
3754 case liacAreaSwitch:
3755 if (lid.num_areas == 2) { lid.curr = 1 - lid.curr; line_input_redraw(); }
3756 return; /*@notreached@*/ break;
3757 case liacMouseFlip: case liacMouseOff: case liacMouseOn:
3758 mouse_flip(liac - liacMouseFlip); return; /*@notreached@*/ break;
3759 }
3760
3761 if (lid_area(flags) & liafEditable) /* the callback will wanna do the work */
3762 { do_call: (lid.callback)(lid.callback_data, liekKey, key); return; }
3763
3764 switch (liac)
3765 { case liacToLeft:
3766 if (lid_area(first) > 0)
3767 { lid_area(first)--; do_redraw: line_input_redraw(); }
3768 break;
3769 case liacToRight:
3770 if (lid_area(first) + lid_area(usable) < lid_area(len))
3771 { lid_area(first)++; goto do_redraw; }
3772 break;
3773 case liacToStart:
3774 if (lid_area(first) > 0) { lid_area(first) = 0; goto do_redraw; }
3775 break;
3776 case liacToEnd:
3777 if (lid_area(first) + lid_area(usable) < lid_area(len))
3778 { lid_area(first) = lid_area(len) - lid_area(usable); goto do_redraw; }
3779 break;
3780 default:
3781 if (lid.curr == lid.num_areas - 1) goto do_call;
3782 break;
3783 }
3784 }
3785
lid_prepare_text(const char * text,tLineInputAreaIndex idx)3786 static void lid_prepare_text(const char* text, tLineInputAreaIndex idx)
3787 { size_t len = strlen(text);
3788 if (len > 1024) len = 1024; /* can't be serious */
3789 if (len > 0)
3790 { char* t = __memory_allocate(len,mapString); /* not storing trailing '\0' */
3791 my_memcpy(t, text, len); lid.area[idx].text = t;
3792 }
3793 lid.area[idx].len = lid.area[idx].maxlen = len;
3794 }
3795
3796 #define line_input_start(a, b, c, d, e, f, g, h) __line_input_start(a,b,c,d,e)
3797
__line_input_start(const char * msg,const char * initstr,tLineInputCallback func,void * data,tLineInputAreaFlags liaf)3798 static void __line_input_start(/*@notnull@*/ const char* msg,
3799 const char* initstr, tLineInputCallback func, void* data,
3800 tLineInputAreaFlags liaf)
3801 { tLineInputAreaIndex count = 0, curr;
3802 my_memclr_var(lid); lid.callback = func; lid.callback_data = data;
3803 lid_prepare_text(msg, count++);
3804 if (liaf & liafEditable)
3805 { if (initstr != NULL) lid_prepare_text(initstr, count);
3806 count++;
3807 }
3808 curr = count - 1; lid.area[curr].flags = liaf; lid.curr = curr;
3809 lid.num_areas = count; key_handling_mode = khmLineInput;
3810 line_input_layout();
3811 if ( (key_handling_mode == khmLineInput) && (liaf & liafEditable) )
3812 line_input_to_eos(); /* IMPROVEME! */
3813 }
3814
3815 #endif /* #if TGC_IS_GRAPHICS */
3816
line_input_ensure_room(void)3817 static void line_input_ensure_room(void)
3818 { if (lid_area(len) >= lid_area(maxlen))
3819 { lid_area(maxlen) += 100;
3820 lid_area(text) = memory_reallocate(lid_area(text), lid_area(maxlen),
3821 mapString);
3822 }
3823 }
3824
line_input_paste_char(char ch)3825 static void line_input_paste_char(char ch)
3826 { char* text;
3827 short count;
3828 if (lid_area(len) >= 1234) return; /* text becomes "too long" */
3829 /* update the string */
3830 line_input_ensure_room();
3831 text = lid_area(text);
3832 for (count = lid_area(len); count > lid_area(pos); count--)
3833 text[count] = text[count - 1];
3834 text[lid_area(pos)++] = ch; lid_area(len)++;
3835 /* update the screen */
3836 if (lid_area(colcurr) < lid_area(colmax)) lid_area(colcurr)++;
3837 else lid_area(first)++;
3838 line_input_redraw();
3839 }
3840
line_input_finish(void)3841 static void line_input_finish(void)
3842 {
3843 #if TGC_IS_CURSES
3844 key_handling_mode = khmCommand;
3845 #endif
3846 redraw_message_line();
3847 __dealloc(lid.area[0].text); __dealloc(lid.area[1].text);
3848 }
3849
uri2filename(const tUriData * u)3850 static const char* uri2filename(const tUriData* u)
3851 /* returns an allocated filename for the URI or NULL */
3852 { const char *retval = NULL, *temp;
3853 const tResourceProtocol rp = u->rp;
3854 if ( (is_rp_nice(rp)) && (rp_data[rp].flags & rpfIsPathHierarchical) &&
3855 ( (temp = u->path) != NULL ) )
3856 { const char* slash = my_strrchr(temp, chDirsep);
3857 if (slash == NULL) retval = temp;
3858 else
3859 { slash++;
3860 if (*slash != '\0') retval = slash;
3861 /* "else": it's probably a directory */
3862 }
3863 }
3864 if (retval != NULL)
3865 { char ch, *ptr = __memory_allocate(strlen(retval) + 1, mapString),
3866 *ptr2 = ptr;
3867 while ( (ch = *retval++) != '\0' )
3868 { if ( (!my_isalnum(ch)) && (ch != '.') && (ch != '-') ) ch = '_';
3869 *ptr++ = ch;
3870 }
3871 *ptr = '\0'; retval = ptr2;
3872 }
3873 return(retval);
3874 }
3875
uristr2filename(const char * str)3876 static const char* uristr2filename(const char* str)
3877 { tUriData* u = uri_parse(str, NULL, NULL, NULL, 0);
3878 const char* filename = uri2filename(u);
3879 uri_put(u);
3880 return(filename);
3881 }
3882
3883 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
ask_download_to(void)3884 static void ask_download_to(void)
3885 { const char* filename = uristr2filename(khmli_download_uri);
3886 line_input_estart(eligDownloadFilename, _("Download to: "), filename, NULL,
3887 _(strUcDownload), _(strUcDownload), _(strCancel));
3888 __dealloc(filename);
3889 }
3890 #endif
3891
start_save_as(const tBrowserDocument * document,const char * filename,tBoolean may_overwrite)3892 static void start_save_as(/*@notnull@*/ const tBrowserDocument* document,
3893 const char* filename, tBoolean may_overwrite)
3894 /* IMPROVEME: show better messages! */
3895 { const tResourceRequest* request = document->request;
3896 tResource* resource = ( (request != NULL) ? request->resource : NULL );
3897 tCantent* cantent = document->cantent;
3898 struct stat statbuf;
3899 int fd, cflags;
3900 if (cantent == NULL)
3901 { errno = 0; bad: show_message_osfailure(errno, _("Can't save")); return; }
3902 cflags = O_CREAT | O_TRUNC | O_WRONLY;
3903 if (!may_overwrite) cflags |= O_EXCL;
3904 fd = my_create(filename, cflags, S_IRUSR | S_IWUSR);
3905 if (fd < 0) goto bad;
3906 if (my_fstat(fd, &statbuf) != 0)
3907 { int e; close_bad: e = errno; my_close(fd); errno = e; goto bad; }
3908 if (!S_ISREG(statbuf.st_mode))
3909 { errno = ( (S_ISDIR(statbuf.st_mode)) ? EISDIR : 0 );
3910 goto close_bad;
3911 }
3912 resource_start_saving(resource, cantent, fd);
3913 show_message(_("Saving"), falsE);
3914 }
3915
interpret_as_html(void)3916 static void interpret_as_html(void)
3917 { tBrowserDocument* document = current_document_x;
3918 if (document != NULL)
3919 { document->bddm = bddmHtml; document_display(document, wrtRedraw); }
3920 }
3921
reload_document(void)3922 static one_caller void reload_document(void)
3923 /* CHECKME: that's unclean! */
3924 { tWindow* window = current_window_x;
3925 if (is_browser_window(window)) wk_browser_reload(window);
3926 }
3927
ask_yesno_act(tConfirmationGoal cg)3928 static one_caller void ask_yesno_act(tConfirmationGoal cg)
3929 { tBrowserDocument* document;
3930 switch (cg)
3931 { case cgQuit: do_quit(); /*@notreached@*/ break;
3932 case cgClose: rcw window_hide(current_window_x, 2); break;
3933 #if TGC_IS_CURSES
3934 case cgOverwriteFile:
3935 document = current_document_x;
3936 if (document != NULL) start_save_as(document, khmli_filename, truE);
3937 break;
3938 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
3939 case cgOverwriteDownload: content_download(truE); break;
3940 #endif
3941 #if CONFIG_EXTRA & EXTRA_DUMP
3942 case cgOverwriteDump:
3943 document = current_document_x;
3944 if (document != NULL) content_dump(document, khmli_filename, truE, NULL);
3945 break;
3946 #endif
3947 #if CONFIG_SESSIONS
3948 case cgOverwriteSession: session_save(khmli_filename, truE); break;
3949 #endif
3950 #endif
3951 case cgSubmit: rcw current_document_form_submit(); break;
3952 case cgReset: current_document_form_reset(); break;
3953 case cgRepost: rcw reload_document(); break;
3954 case cgHtml: rcw interpret_as_html(); break;
3955 case cgEnable: rcw document = current_document_x;
3956 #if TGC_IS_GRAPHICS
3957 /* IMPLEMENTME! */
3958 #else
3959 if ( (document != NULL) && (document->aecur != INVALID_AE) )
3960 document->cantent->aebase[document->aecur].flags &= ~aefDisabled;
3961 #endif
3962 break;
3963 }
3964 }
3965
3966 typedef struct
3967 {
3968 #if CONFIG_USER_QUERY
3969 tUserQuery* query;
3970 #endif
3971 tConfirmationGoal cg;
3972 } tAskYesnoData;
3973
3974 #if CONFIG_USER_QUERY
3975
user_query_removal(void * _query,tDhmNotificationFlags flags)3976 static void user_query_removal(void* _query, tDhmNotificationFlags flags)
3977 { tUserQuery* query = (tUserQuery*) _query;
3978 query->mif |= mifObjectVanished;
3979 (lid.callback)(lid.callback_data, liekFail, 0);
3980 }
3981
user_query_push(tUserQuery * query)3982 static void user_query_push(tUserQuery* query)
3983 { dhm_notification_off(query->resource, user_query_removal, query);
3984 (query->callback)(query);
3985 }
3986
3987 #endif
3988
ask_yesno_callback(void * _data,tLineInputEventKind liek,tKey key)3989 static void ask_yesno_callback(void* _data, tLineInputEventKind liek, tKey key)
3990 { tAskYesnoData* data = (tAskYesnoData*) _data;
3991 #if CONFIG_USER_QUERY
3992 tUserQuery* query = data->query;
3993 #endif
3994 tConfirmationGoal cg = data->cg;
3995 switch (liek)
3996 { case liekFail:
3997 #if CONFIG_USER_QUERY
3998 if (query != NULL) query->mif |= mifQueryFailed;
3999 #endif
4000 goto do_cancel0; /*@notreached@*/ break;
4001 case liekCancel:
4002 do_cancel: {}
4003 #if CONFIG_USER_QUERY
4004 if (query != NULL) query->mif |= mifUserCancelled;
4005 #endif
4006 do_cancel0: line_input_finish(); memory_deallocate(data);
4007 #if CONFIG_USER_QUERY
4008 if (query != NULL) { handle_conf_query: user_query_push(query); }
4009 #endif
4010 break;
4011 #if TGC_IS_CURSES
4012 case liekKey:
4013 if ( (key == config.char_yes) || (key == KEY_ENTER) )
4014 { line_input_finish(); memory_deallocate(data);
4015 #if CONFIG_USER_QUERY
4016 if (query != NULL) { goto handle_conf_query; } else
4017 #endif
4018 { ask_yesno_act(cg); }
4019 }
4020 else if (key == config.char_no) goto do_cancel;
4021 break;
4022 #endif
4023 }
4024 }
4025
ask_yesno(const char * msg,tConfirmationGoal cg,tUserQuery * query)4026 static void ask_yesno(const char* msg, tConfirmationGoal cg, tUserQuery* query)
4027 { tAskYesnoData* data = memory_allocate(sizeof(tAskYesnoData), mapLineInput);
4028 #if TGC_IS_GRAPHICS
4029 #define buf msg /* (no string change necessary) */
4030 #else
4031 char tempbuf[1024], *spfbuf;
4032 my_spf(tempbuf, sizeof(tempbuf), &spfbuf, "%s (%c/%c) ", msg,
4033 config.char_yes, config.char_no);
4034 #define buf spfbuf
4035 #endif
4036 #if CONFIG_USER_QUERY
4037 data->query = query;
4038 #endif
4039 data->cg = cg;
4040 line_input_start(buf, NULL, ask_yesno_callback, data, liafNone,
4041 _("Confirmation Required"), istrYesUc, istrNoUc);
4042 #undef buf
4043 #if !TGC_IS_GRAPHICS
4044 my_spf_cleanup(tempbuf, spfbuf);
4045 #endif
4046 }
4047
4048 typedef struct
4049 {
4050 #if CONFIG_USER_QUERY
4051 tUserQuery* query;
4052 #endif
4053 tEditableLineInputGoal elig;
4054 } tEstartData;
4055
4056 #if CONFIG_USER_QUERY
4057
do_start_user_query_login(tUserQuery * query)4058 static void do_start_user_query_login(tUserQuery* query)
4059 { tResource* resource = query->resource;
4060 tMissingInformationFlags mif = query->mif;
4061 tBoolean pr = cond2boolean(mif & mifProxyrelated);
4062 const char *ps = (pr ? _("proxy") : _("server")), *lf, *what, *sep,
4063 *old_username, *userat; /* user_at? use_rat? :-) */
4064 char schemebuf[MAXSCHEMESTRSIZE], * spfbuf;
4065 tEditableLineInputGoal elig;
4066 if (mif & mifPriorLoginAttemptFailed)
4067 { lf = (pr ? _("Proxy login failed") : _(strLoginFailed));
4068 sep = strSpacedDash;
4069 }
4070 else lf = sep = strEmpty;
4071
4072 rp2scheme(resource->protocol, schemebuf); old_username = userat = strEmpty;
4073 if (mif & mifUsername) { elig = eligUsername; what = _("username"); }
4074 else if (mif & mifPassword)
4075 { const char* u = resource->uri_data->username;
4076 if (u != NULL) { old_username = u; userat = "@"; }
4077 elig = eligPassword; what = _("password");
4078 }
4079 else
4080 { /* bug */
4081 return;
4082 }
4083
4084 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("%s%s%s %s for %s %s%s%s:%d: "), lf,
4085 sep, schemebuf, what, ps, old_username, userat, query->hostname,
4086 ntohs(query->portnumber));
4087 line_input_estart(elig, spfbuf, NULL, query, _("User Input"), _("Apply"),
4088 _(strCancel));
4089 my_spf_cleanup(strbuf, spfbuf);
4090 }
4091
user_query_queue(tUserQuery * query)4092 void user_query_queue(tUserQuery* query)
4093 { tResource* resource = query->resource;
4094 #if TGC_IS_CURSES
4095 if (key_handling_mode == khmLineInput)
4096 { /* IMPLEMENTME: add a tRemainingWork entry instead of failing! */
4097 query->mif |= mifQueryFailed; return;
4098 }
4099 #endif
4100 #if CONFIG_FTP && OPTION_TLS
4101 if (query->mif & mifFtpsDataclear)
4102 { const char* hostname = query->hostname;
4103 char schemebuf[MAXSCHEMESTRSIZE];
4104 if (strlen(hostname) > STRBUF_SIZE / 4) /* can't be serious */
4105 hostname = "[?]";
4106 rp2scheme(resource->protocol, schemebuf);
4107 sprint_safe(strbuf,
4108 _("%s server %s:%d can't protect data - allow cleartext?"),
4109 schemebuf, hostname, ntohs(query->portnumber));
4110 ask_yesno(strbuf, cgFtpsDataclear, query);
4111 }
4112 else
4113 #endif
4114 { do_start_user_query_login(query); }
4115 #if TGC_IS_CURSES
4116 if (key_handling_mode == khmLineInput)
4117 { dhm_notification_setup(resource, user_query_removal, query, dhmnfRemoval,
4118 dhmnSet);
4119 }
4120 #endif
4121 }
4122
start_user_query_username(void)4123 static void start_user_query_username(void)
4124 /* go from "asking for password" back to "ask for username"; CHECKME: dirty! */
4125 { const tEstartData* data = (const tEstartData*) (lid.callback_data);
4126 tUserQuery* query = data->query;
4127 memory_deallocate(data); line_input_finish();
4128 query->mif &= ~mifPassword; query->mif |= mifUsername;
4129 do_start_user_query_login(query);
4130 }
4131
4132 #endif /* #if CONFIG_USER_QUERY */
4133
4134 #if CONFIG_JUMPS
4135
4136 static /*@null@*/ const char* previous_jump = NULL;
4137
lookup_jump_shortcut(const char * shortcut)4138 static tConfigJump* lookup_jump_shortcut(const char* shortcut)
4139 { tConfigJump* j = config.jumps;
4140 while (j != NULL)
4141 { const char* name = j->name;
4142 if ( (name != NULL) && (!strcmp(name, shortcut)) ) break; /* found */
4143 j = j->next;
4144 }
4145 return(j);
4146 }
4147
handle_lig_jump(const char * text)4148 static one_caller void handle_lig_jump(const char* text)
4149 { char *text2 SHUT_UP_COMPILER(NULL), *uri SHUT_UP_COMPILER(NULL);
4150 const tConfigJump* j;
4151 tBoolean cleanup_text2, cleanup_uri;
4152 cleanup_text2 = cleanup_uri = falsE; my_strdedup(previous_jump, text);
4153 if (my_strchr(text, ' ') == NULL) /* no arguments given, just the shortcut */
4154 { j = lookup_jump_shortcut(text);
4155 if (j != NULL)
4156 { uri = j->uri;
4157 if ( (uri != NULL) && (*uri != '\0') )
4158 { prr:
4159 wk_browser_prr(require_browser_window(), uri, prrfRedrawOne, NULL);
4160 }
4161 else { bad_uri: show_message(_(strResourceError[reUri]), truE); }
4162 }
4163 else
4164 { char* spfbuf;
4165 not_found:
4166 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
4167 _("Can't find the jump shortcut \"%s\""), text);
4168 show_message(spfbuf, truE);
4169 my_spf_cleanup(strbuf, spfbuf);
4170 }
4171 }
4172 else /* need to extract shortcut and replace markers with argument values */
4173 { char *ptr, *ptr2;
4174 unsigned short count;
4175 tBoolean is_last;
4176 text2 = my_strdup(text); cleanup_text2 = truE;
4177 ptr = text2; ptr2 = my_strchr(ptr, ' ');
4178 if (ptr2 == NULL) /* "can't happen" */
4179 { not_found2:
4180 text = ptr; goto not_found;
4181 }
4182 *ptr2 = '\0';
4183 j = lookup_jump_shortcut(ptr);
4184 if (j == NULL) goto not_found2;
4185 uri = j->uri;
4186 if ( (uri == NULL) || (*uri == '\0') ) goto bad_uri;
4187 uri = my_strdup(uri); cleanup_uri = truE;
4188 /* Try to replace all markers in the URI with arguments: */
4189 is_last = falsE;
4190 for (count = 0; count < j->argcount; count++)
4191 { const char *marker, *match, *src;
4192 char *new_uri, *dest;
4193 size_t markerlen, size;
4194 if (is_last) break; /* no more arguments given */
4195 spaceloop:
4196 ptr = ptr2 + 1;
4197 if (*ptr == '\0') break;
4198 ptr2 = my_strchr(ptr, ' ');
4199 if (ptr2 != NULL)
4200 { if (ptr2 == ptr) goto spaceloop; /* skip accidental space sequence */
4201 *ptr2 = '\0';
4202 }
4203 else
4204 { ptr2 = ptr;
4205 while (*ptr2 != '\0') ptr2++; /* set it onto the trailing '\0' byte */
4206 is_last = truE;
4207 }
4208 marker = j->arg[count];
4209 if ( (marker == NULL) || (*marker == '\0') ) /* "can't happen" */
4210 continue;
4211 match = my_strstr(uri, marker);
4212 if (match == NULL)
4213 { char* spfbuf;
4214 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
4215 _("Can't find marker \"%s\" in URL pattern"), marker);
4216 show_message(spfbuf, truE);
4217 my_spf_cleanup(strbuf, spfbuf);
4218 goto cleanup;
4219 }
4220 markerlen = strlen(marker);
4221 size = strlen(uri) - markerlen + strlen(ptr) + 1;
4222 new_uri = __memory_allocate(size, mapString);
4223 src = uri; dest = new_uri;
4224 while (src < match) *dest++ = *src++;
4225 while (ptr < ptr2) *dest++ = *ptr++;
4226 src += markerlen;
4227 while ( (*dest++ = *src++) != '\0' ) { /* nothing */ }
4228 memory_deallocate(uri); uri = new_uri;
4229 }
4230 goto prr;
4231 }
4232 cleanup:
4233 if (cleanup_text2) memory_deallocate(text2);
4234 if (cleanup_uri) memory_deallocate(uri);
4235 }
4236
4237 #endif /* #if CONFIG_JUMPS */
4238
4239 #if OPTION_EXECEXT & EXECEXT_SHELL
4240
4241 static const char strNoShellCmd[] = N_("No shell command given"),
4242 strExecextShellColon[] = "execext-shell:";
4243
execext_shell_header(void)4244 static const char* execext_shell_header(void)
4245 { char* spfbuf;
4246 my_spf(strbuf2, STRBUF_SIZE, &spfbuf, "V:%s\n\n", strSoftwareId);
4247 return(spfbuf);
4248 }
4249
execext_shell_request_callback(void * _document,tDhmNotificationFlags flags)4250 static void execext_shell_request_callback(void* _document,
4251 tDhmNotificationFlags flags)
4252 { tBrowserDocument* document = (tBrowserDocument*) _document;
4253 if (flags & (dhmnfDataChange | dhmnfMetadataChange))
4254 { const tResourceRequest* request = document->request;
4255 const tResource* resource = request->resource;
4256 tResourceError re;
4257 if (resource != NULL)
4258 { const char* msg;
4259 tBoolean is_err;
4260 if ( (re = resource->error) != reFine )
4261 { handle_re: msg = _(strResourceError[re]); is_err = truE;
4262 set_msg:
4263 wk_info_set_message((tWindow*)(document->container), msg, is_err);
4264 }
4265 else if (resource->flags & rfFinal)
4266 { msg = _(strResourceState[rsComplete]); is_err = falsE; goto set_msg; }
4267 }
4268 else if ( (re = request->error) != reFine ) goto handle_re;
4269 document_display(document, wrtRedraw);
4270 }
4271 else if (flags & dhmnfAttachery) /* a resource was attached to the request */
4272 { tResource* resource = document->request->resource;
4273 dhm_notification_setup(resource, execext_shell_request_callback, _document,
4274 dhmnfDataChange | dhmnfMetadataChange, dhmnSet);
4275 cantent_put(document->cantent);
4276 cantent_attach(document->cantent, resource->cantent);
4277 }
4278 }
4279
handle_lig_execext_shell(const char * text)4280 static one_caller void handle_lig_execext_shell(const char* text)
4281 { tExecextShellData data;
4282 tExecextShellFlags esf = esfNone;
4283 const tBrowserDocument* document;
4284 const tCantent* cantent;
4285 const tContentblock *cont SHUT_UP_COMPILER(NULL), *cont2;
4286 const char* spfbuf_header = strEmpty;
4287 size_t header_len, content_size, writedata_size;
4288 tBoolean must_dealloc_command, want_header, want_content, interpret_percents;
4289 must_dealloc_command = want_header = want_content = interpret_percents=falsE;
4290 if (*text == ':') /* options given */
4291 { char ch;
4292 while ( (ch = *++text) != '\0' )
4293 { switch (ch)
4294 { case 'o': esf |= esfReadStdout; break;
4295 case 'e': esf |= esfReadStderr; break;
4296 case 'H': esf |= esfEnforceHtml; break;
4297 case 'h': want_header = truE; break;
4298 case 'c': want_content = truE; break;
4299 case 'i': interpret_percents = truE; break;
4300 case ' ': text++; goto end_of_options; /*@notreached@*/ break;
4301 }
4302 }
4303 end_of_options: {}
4304 }
4305 if (*text == '\0') { show_message(_(strNoShellCmd), truE); return; }
4306 #if !DO_WK_INFO
4307 esf &= ~(esfReadStdout | esfReadStderr);
4308 /* feature not available; CHECKME: show error message instead? */
4309 #endif
4310 my_memclr_var(data); data.esf = esf; document = current_document_x;
4311 cantent = ( (document != NULL) ? document->cantent : NULL );
4312
4313 if (!interpret_percents) { simple_command: data.command = text; }
4314 else
4315 { const char *temp = my_strstr(text, "%U"), *uri;
4316 char* convcomm;
4317 if (temp == NULL) goto simple_command;
4318 if ( (document != NULL) && ( (uri = document->title) != NULL ) && (*uri) )
4319 { const size_t len_before = temp - text, len_uri = strlen(uri),
4320 len_after = strlen(temp + 2);
4321 char* ptr = convcomm = __memory_allocate(len_before + len_uri + len_after
4322 + 1, mapString);
4323 if (len_before > 0) { my_memcpy(ptr, text, len_before); ptr+=len_before;}
4324 my_memcpy(ptr, uri, len_uri); ptr += len_uri;
4325 if (len_after > 0) { my_memcpy(ptr, temp+2, len_after); ptr+=len_after; }
4326 *ptr = '\0';
4327 }
4328 else /* just remove the "%U" */
4329 { size_t len_before = temp - text;
4330 convcomm = __memory_allocate(strlen(text) - 2 + 1, mapString);
4331 if (len_before > 0) my_memcpy(convcomm, text, len_before);
4332 strcpy(convcomm + len_before, temp + 2);
4333 }
4334 data.command = convcomm; must_dealloc_command = truE;
4335 }
4336
4337 #if DO_WK_INFO
4338 if (esf & (esfReadStdout | esfReadStderr))
4339 { tPrrData prr_data;
4340 tResourceRequest* request;
4341 tResourceError re;
4342 tWindow* iwindow;
4343 tBrowserDocument* idocument;
4344 const tBrowserDocumentDisplayMode bddm = ( (esf & esfEnforceHtml) ?
4345 bddmHtml : bddmSource );
4346 char* spfbuf;
4347 prr_setup(&prr_data, strExecextShellColon /* dummy - CHECKME! */,
4348 prrfUpsfp4);
4349 prepare_resource_request(&prr_data);
4350 if ( (re = prr_data.result->error) != reFine )
4351 { show_message(_(strResourceError[re]), truE);
4352 prr_setdown(&prr_data); goto cleanup;
4353 }
4354 request = data.request = prr_data.result; prr_data.result = NULL;
4355 prr_setdown(&prr_data);
4356 my_spf(NULL, 0, &spfbuf, _("Result of \"%s\""), text);
4357 iwindow = visible_window_x[1 - current_window_index_x] =
4358 wk_info_create(my_spf_use(spfbuf), bddm, &idocument);
4359 idocument->request = request;
4360 dhm_notification_setup(request, execext_shell_request_callback, idocument,
4361 dhmnfDataChange | dhmnfMetadataChange | dhmnfAttachery, dhmnSet);
4362 wk_info_finalize(iwindow);
4363 }
4364 #endif
4365
4366 header_len = content_size = writedata_size = 0;
4367 if (want_header)
4368 { spfbuf_header = execext_shell_header(); header_len = strlen(spfbuf_header);
4369 }
4370 if (want_content)
4371 { if (cantent != NULL)
4372 { cont2 = cont = cantent->content;
4373 while (cont2 != NULL) { content_size += cont2->used; cont2=cont2->next; }
4374 }
4375 if (content_size <= 0) want_content = falsE; /* no content there */
4376 }
4377 writedata_size = header_len + content_size;
4378 if (writedata_size > 0) /* user wants to write something (header/content) */
4379 { char* writedata = __memory_allocate(writedata_size, mapWritedata), *temp;
4380 data.writedata = writedata; data.writedata_size = writedata_size;
4381 if (header_len > 0) my_memcpy(writedata, spfbuf_header, header_len);
4382 temp = writedata + header_len; cont2 = cont;
4383 while (cont2 != NULL)
4384 { const size_t usedsize = cont2->used;
4385 if (usedsize > 0)
4386 { my_memcpy(temp, cont2->data, usedsize); temp += usedsize; }
4387 cont2 = cont2->next;
4388 }
4389 }
4390 if (spfbuf_header != strEmpty) my_spf_cleanup(strbuf2, spfbuf_header);
4391
4392 resource_start_execext_shell(&data);
4393 cleanup:
4394 if (must_dealloc_command) memory_deallocate(data.command);
4395 __dealloc(data.writedata);
4396 }
4397
4398 #endif /* #if OPTION_EXECEXT & EXECEXT_SHELL */
4399
test_local_file_overwrite(char ** _filename,tConfirmationGoal cg,tBoolean * _may_overwrite)4400 static tBoolean test_local_file_overwrite(/*const*/ char** _filename,
4401 tConfirmationGoal cg, /*@out@*/ tBoolean* _may_overwrite)
4402 /* determines whether a local file may be overwritten right now; if not, this
4403 function either prepares a "may overwrite?" question for the user or
4404 produces an error message */
4405 { tBoolean retval;
4406 const char* filename = *_filename;
4407 struct stat statbuf;
4408 int err;
4409
4410 retval = *_may_overwrite = falsE;
4411 err = my_stat(filename, &statbuf);
4412 if (err != 0)
4413 { if (err != -1) errno = 0; /* "should not happen" */
4414 if (errno == ENOENT) { allow: retval = truE; } /* nothing there */
4415 else { failed: show_message_osfailure(errno, NULL); }
4416 }
4417 else /* already there */
4418 { const mode_t mode = statbuf.st_mode;
4419 if (!S_ISREG(mode)) /* only overwrite _files_ */
4420 { errno = ( (S_ISDIR(mode)) ? EISDIR : 0 ); goto failed; }
4421 else if (config.flags & cfDontConfirmOverwrite)
4422 { *_may_overwrite = truE; goto allow; }
4423 else
4424 { char bstr[1024];
4425 const char* bstrptr;
4426 const off_t size = statbuf.st_size;
4427 __dealloc(khmli_filename); khmli_filename = filename; *_filename = NULL;
4428 if (size < 0) bstrptr = strEmpty; /* "should not happen" */
4429 else
4430 { sprint_safe(bstr,strBracedNumstr,localized_size(size),bytebytes(size));
4431 bstrptr = bstr;
4432 }
4433 sprint_safe(strbuf, _("Overwrite existing%s?"), bstrptr);
4434 ask_yesno(strbuf, cg, NULL);
4435 }
4436 }
4437 return(retval);
4438 }
4439
line_input_estart_act(tEditableLineInputGoal elig,char ** _text)4440 static one_caller void line_input_estart_act(tEditableLineInputGoal elig,
4441 /*const*/ char** _text)
4442 { const char* text = *_text;
4443 tBrowserDocument* document;
4444 tWindowRedrawingTask wrt;
4445 tUriData* uri_data;
4446 tResourceError re;
4447 switch (elig)
4448 { case eligGoToUri:
4449 wk_browser_prr(require_browser_window(), text, prrfRedrawOne, NULL);
4450 break;
4451 case eligSaveAs:
4452 { tBoolean may_overwrite;
4453 document = current_document_x;
4454 if ( (document != NULL) && (test_local_file_overwrite(_text,
4455 cgOverwriteFile, &may_overwrite)) )
4456 start_save_as(document, text, may_overwrite);
4457 }
4458 break;
4459 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
4460 case eligDownloadUri:
4461 uri_data = uri_parse(text, NULL, NULL, NULL, 0);
4462 re = uri_data->re; uri_put(uri_data);
4463 if (re != reFine) { show_message(_(strResourceError[re]),truE); return; }
4464 __dealloc(khmli_download_uri); khmli_download_uri = text; *_text = NULL;
4465 ask_download_to();
4466 break;
4467 case eligDownloadFilename:
4468 { tBoolean may_overwrite;
4469 if(test_local_file_overwrite(_text,cgOverwriteDownload,&may_overwrite))
4470 __content_download(text, may_overwrite);
4471 }
4472 break;
4473 #endif
4474 #if CONFIG_EXTRA & EXTRA_DUMP
4475 case eligDumpFilename:
4476 { tBoolean may_overwrite;
4477 if (test_local_file_overwrite(_text, cgOverwriteDump, &may_overwrite))
4478 { if ( (document = current_document_x) != NULL )
4479 content_dump(document, text, may_overwrite, NULL);
4480 }
4481 }
4482 break;
4483 #endif
4484 case eligDocumentSearch:
4485 wrt = wrtSearch;
4486 handle_search:
4487 __dealloc(search_string); search_string = text; *_text = NULL;
4488 if ( (document = current_document_x) != NULL )
4489 document_display(document, wrt);
4490 break;
4491 case eligDocumentSearchBackward:
4492 wrt = wrtSearchBackward; goto handle_search; /*@notreached@*/ break;
4493 #if CONFIG_JUMPS
4494 case eligJump: handle_lig_jump(text); break;
4495 #endif
4496 #if TGC_IS_CURSES
4497 case eligFormText: case eligFormPassword: case eligFormFilename:
4498 { tActiveElement* aes;
4499 tActiveElementNumber _ae;
4500 if ( (document = current_document_x) == NULL ) return;
4501 aes = document->active_element; _ae = document->aecur;
4502 if (*text != '\0') my_strdedup(aes[_ae].current_text, text);
4503 else dealloc(aes[_ae].current_text);
4504 /* IMPLEMENTME: "javascript_handle_event(jekChange, _ae);" if
4505 appropriate */
4506 javascript_handle_event(jekBlur, _ae);
4507 document_display(document, wrtRedraw);
4508 }
4509 break;
4510 #endif
4511 #if CONFIG_SESSIONS
4512 case eligSessionSave:
4513 { tBoolean may_overwrite;
4514 if (test_local_file_overwrite(_text,cgOverwriteSession,&may_overwrite))
4515 session_save(text, may_overwrite);
4516 }
4517 break;
4518 case eligSessionResume: session_resume(text); break;
4519 #endif
4520 #if OPTION_EXECEXT & EXECEXT_SHELL
4521 case eligExecextShell: handle_lig_execext_shell(text); break;
4522 #endif
4523 }
4524 }
4525
4526 #if TGC_IS_CURSES
__curleft(void)4527 static my_inline void __curleft(void)
4528 { (void) move(stdscr->_cury, stdscr->_curx - 1);
4529 }
__curright(void)4530 static my_inline void __curright(void)
4531 { (void) move(stdscr->_cury, stdscr->_curx + 1);
4532 }
4533 #endif
4534
line_input_estart_callback(void * _data,tLineInputEventKind liek,tKey key)4535 static void line_input_estart_callback(void* _data, tLineInputEventKind liek,
4536 tKey key)
4537 { tEstartData* data = (tEstartData*) _data;
4538 #if CONFIG_USER_QUERY
4539 tUserQuery* query = data->query;
4540 #endif
4541 tEditableLineInputGoal elig = data->elig;
4542 tLineInputActionCode liac;
4543 switch (liek)
4544 { case liekFail:
4545 #if CONFIG_USER_QUERY
4546 if (query != NULL) query->mif |= mifQueryFailed;
4547 #endif
4548 goto do_cancel0; /*@notreached@*/ break;
4549 case liekCancel:
4550 do_cancel: {}
4551 #if CONFIG_USER_QUERY
4552 if (query != NULL) query->mif |= mifUserCancelled;
4553 #endif
4554 do_cancel0: line_input_finish(); memory_deallocate(data);
4555 #if CONFIG_USER_QUERY
4556 if (query != NULL) user_query_push(query);
4557 #endif
4558 break;
4559 #if TGC_IS_CURSES
4560 case liekKey:
4561 liac = lookup_lineinput_key(key);
4562 switch (liac)
4563 { case liacUnknown: break; /* the most likely case first */
4564 case liacCancel: goto do_cancel; /*@notreached@*/ break;
4565 case liacToLeft:
4566 if (lid_area(pos) > 0)
4567 { lid_area(pos)--;
4568 if (lid_area(colcurr) > lid_area(colmin))
4569 { __curleft(); lid_area(colcurr)--; }
4570 else { lid_area(first)--; line_input_redraw(); }
4571 }
4572 return; /*@notreached@*/ break;
4573 case liacToRight:
4574 if (lid_area(pos) < lid_area(len))
4575 { lid_area(pos)++;
4576 if (lid_area(colcurr) < lid_area(colmax))
4577 { __curright(); lid_area(colcurr)++; }
4578 else { lid_area(first)++; line_input_redraw(); }
4579 }
4580 return; /*@notreached@*/ break;
4581 case liacToStart:
4582 if (lid_area(pos) > 0)
4583 { lid_area(pos) = lid_area(first) = 0;
4584 lid_area(colcurr) = lid_area(colmin);
4585 line_input_redraw();
4586 }
4587 return; /*@notreached@*/ break;
4588 case liacToEnd: line_input_to_eos(); return; /*@notreached@*/ break;
4589 case liacPass2user:
4590 #if CONFIG_USER_QUERY
4591 if ( (elig == eligPassword) && (query != NULL) )
4592 start_user_query_username();
4593 #endif
4594 return; /*@notreached@*/ break;
4595 }
4596
4597 if (key == KEY_ENTER)
4598 { if ( (lid_area(len) > 0) || (lid_area(flags) & liafAllowEmptyText) )
4599 { char* text;
4600 line_input_ensure_room(); text = lid_area(text);
4601 text[lid_area(len)] = '\0'; lid_area(text) = NULL;
4602 line_input_finish(); memory_deallocate(data);
4603 #if CONFIG_DEBUG
4604 sprint_safe(strbuf, "line input: *%s*\n", text);
4605 debugmsg(strbuf);
4606 #endif
4607 #if CONFIG_USER_QUERY
4608 if (query != NULL)
4609 { tResource* resource = query->resource;
4610 query->mif &= ~mifSet;
4611 if (elig == eligUsername)
4612 { resource->uri_data->username = text; text = NULL;
4613 query->mif |= mifUsername;
4614 }
4615 else if (elig == eligPassword)
4616 { resource->uri_data->password = text; text = NULL;
4617 query->mif |= mifPassword;
4618 }
4619 user_query_push(query);
4620 }
4621 else
4622 #endif
4623 { line_input_estart_act(elig, &text); }
4624 __dealloc(text);
4625 }
4626 }
4627 else if (key == KEY_BACKSPACE)
4628 { if (lid_area(pos) > 0)
4629 { char* text = lid_area(text);
4630 short count;
4631 /* update the string */
4632 for (count = lid_area(pos); count < lid_area(len); count++)
4633 text[count - 1] = text[count];
4634 lid_area(pos)--; lid_area(len)--;
4635 /* update the screen */
4636 if (lid_area(colcurr) <= lid_area(colmin)) lid_area(first)--;
4637 else { lid_area(colcurr)--; line_input_redraw(); }
4638 }
4639 }
4640 else if (key == KEY_DC)
4641 { if (lid_area(pos) < lid_area(len))
4642 { char* text = lid_area(text);
4643 short count;
4644 /* update the string */
4645 for (count = lid_area(pos); count < lid_area(len) - 1; count++)
4646 text[count] = text[count + 1];
4647 lid_area(len)--;
4648 /* update the screen */
4649 line_input_redraw();
4650 }
4651 }
4652 else if ( (key >= ' ') && (key <= 255) && (key != 127) )
4653 { /* CHECKME: OPTION_CED? */
4654 line_input_paste_char(key);
4655 }
4656 break;
4657 #endif /* #if TGC_IS_CURSES */
4658 }
4659 }
4660
__line_input_estart(tEditableLineInputGoal elig,const char * msg,const char * initstr,tUserQuery * query)4661 static void __line_input_estart(tEditableLineInputGoal elig, const char* msg,
4662 const char* initstr, tUserQuery* query)
4663 { tLineInputAreaFlags liaf = liafEditable;
4664 tEstartData* data = __memory_allocate(sizeof(tEstartData), mapLineInput);
4665 #if CONFIG_USER_QUERY
4666 data->query = query;
4667 #endif
4668 data->elig = elig;
4669 #if TGC_IS_CURSES
4670 if (is_disguising_elig(elig)) liaf |= liafDisguising;
4671 if (is_emptyok_elig(elig)) liaf |= liafAllowEmptyText;
4672 #endif
4673 __line_input_start(msg, initstr, line_input_estart_callback, data, liaf);
4674 }
4675
4676
4677 /** Keys and command codes */
4678
remap_key(tKey * _key)4679 static one_caller void remap_key(tKey* _key)
4680 { switch (*_key)
4681 { case KEY_ESCAPE: *_key = KEY_CANCEL; break;
4682 case '\015': case '\012': *_key = KEY_ENTER; break;
4683 case '\010': *_key = KEY_BACKSPACE; break;
4684 case 127: *_key = KEY_DC; break;
4685 }
4686 }
4687
generic_handle_command(tProgramCommandCode code)4688 static tBoolean generic_handle_command(tProgramCommandCode code)
4689 /* if the window-specific handler didn't like the command... */
4690 { tBoolean retval = truE;
4691 const char *temp, *text, *msg, *uri;
4692 tEditableLineInputGoal elig;
4693 tBrowserDocument* document;
4694 tLinenumber scrolloff;
4695 switch (code)
4696 {case pccScreenUnsplit:
4697 if (visible_window_x[1] != NULL)
4698 window_hide(visible_window_x[1 - current_window_index_x], 1);
4699 break;
4700 case pccScreenSplit:
4701 if (visible_window_x[1] == NULL)
4702 { if ( (document = current_document_x) != NULL )
4703 { const tActiveElementNumber _ae = document->aecur;
4704 if (_ae != INVALID_AE) do_activate_element(document, _ae, falsE);
4705 }
4706 visible_window_x[1] = wk_browser_create(); current_window_index_x = 1;
4707 window_redraw_all();
4708 }
4709 break;
4710 case pccScreenSwitch:
4711 if (visible_window_x[1] != NULL)
4712 { current_window_index_x = 1 - current_window_index_x;
4713 redraw_message_line();
4714 }
4715 break;
4716 case pccWindowNext:
4717 { tWindow* window = current_window_x;
4718 tBoolean is_first_try = truE;
4719 findloop1: window = window->next;
4720 if (window == NULL) window = windowlisthead;
4721 if (window != NULL)
4722 { if (window_is_visible(window))
4723 { if (is_first_try) { is_first_try = falsE; goto findloop1; }
4724 else goto out;
4725 }
4726 current_window_x = window; window_redraw(current_window_x);
4727 }
4728 }
4729 break;
4730 case pccWindowPrevious:
4731 { tWindow* window = current_window_x;
4732 tBoolean is_first_try = truE;
4733 findloop2: window = window->prev;
4734 if (window == NULL) window = windowlisttail;
4735 if (window != NULL)
4736 { if (window_is_visible(window))
4737 { if (is_first_try) { is_first_try = falsE; goto findloop2; }
4738 else goto out;
4739 }
4740 current_window_x = window; window_redraw(current_window_x);
4741 }
4742 }
4743 break;
4744 case pccMenuWindowlist:
4745 #if CONFIG_MENUS & MENUS_WLIST
4746 cm_setup_window_list();
4747 #else
4748 menus_were_disabled();
4749 #endif
4750 break;
4751 case pccMenuContextual:
4752 #if CONFIG_MENUS & MENUS_CONTEXT
4753 { tActiveElementNumber _ae;
4754 document = current_document_x;
4755 _ae = ( (document != NULL) ? (document->aecur) : INVALID_AE );
4756 cm_setup_contextual(0, ( (current_window_index_x == 1) ? (VMIDDLE + 1) :
4757 0 ), _ae);
4758 }
4759 #else
4760 menus_were_disabled();
4761 #endif
4762 break;
4763 case pccMenuUriHistory:
4764 #if CONFIG_MENUS & MENUS_UHIST
4765 cm_setup_uri_history();
4766 #else
4767 menus_were_disabled();
4768 #endif
4769 break;
4770 case pccGoUri:
4771 text = _("Go to URL: "); temp = NULL;
4772 go_to_uri:
4773 line_input_estart(eligGoToUri, text, temp, NULL, _("Enter URL"), _("Go"),
4774 _(strCancel));
4775 break;
4776 case pccGoUriPreset:
4777 { if ( (document = current_document_x) != NULL )
4778 { text = _("Edit URL: "); temp = document->title; goto go_to_uri; }
4779 }
4780 break;
4781 case pccGoHome:
4782 if ( (uri = config.home_uri) != NULL )
4783 { do_uri: wk_browser_prr(require_browser_window(),uri,prrfRedrawOne,NULL);}
4784 else show_message(_("No home URL configured"), truE);
4785 break;
4786 case pccGoSearch:
4787 if ( (uri = config.search_engine) != NULL ) goto do_uri;
4788 else show_message(_("No search engine URL configured"), truE);
4789 break;
4790 case pccGoBookmarks:
4791 if ( (uri = config.bookmarks) != NULL ) goto do_uri;
4792 else show_message(_("No bookmarks document URL configured"), truE);
4793 break;
4794 #if CONFIG_JUMPS
4795 case pccJump:
4796 temp = NULL;
4797 handle_jump:
4798 if (config.jumps != NULL)
4799 { line_input_estart(eligJump, _("Jump to: "), temp, NULL, _("Jump to URL"),
4800 _("Jump"), _(strCancel));
4801 }
4802 else show_message(_("No jump shortcuts configured"), truE);
4803 break;
4804 case pccJumpPreset:
4805 temp = previous_jump; goto handle_jump; /*@notreached@*/ break;
4806 #else
4807 case pccJump: case pccJumpPreset: fwdact(_(strFjumps)); break;
4808 #endif
4809 case pccWindowClose:
4810 if (config.flags & cfDontConfirmClose) window_hide(current_window_x, 2);
4811 else ask_yesno(_("Really close this window?"), cgClose, NULL);
4812 break;
4813 case pccWindowNew:
4814 { const tWindow* window = current_window_x;
4815 if ( (!is_browser_window(window)) || (window_is_precious(window)) )
4816 { current_window_x = wk_browser_create();
4817 window_redraw(current_window_x);
4818 }
4819 }
4820 break;
4821 case pccWindowNewFromDocument:
4822 { document = current_document_x;
4823 if ( (document != NULL) && ( (uri = document->title) != NULL ) )
4824 { current_window_x = wk_browser_create();
4825 wk_browser_prr(current_window_x, uri, prrfRedrawOne, NULL);
4826 }
4827 }
4828 break;
4829 case pccDocumentSave:
4830 { tCantent* cantent;
4831 if ( ( (document = current_document_x) != NULL ) &&
4832 ( (cantent = document->cantent) != NULL ) )
4833 { const char* filename;
4834 uri = document->title; filename = (uri ? uristr2filename(uri) : NULL);
4835 line_input_estart(eligSaveAs, _("Save document as: "), filename, NULL,
4836 _("Save Document"), _(strSave), _(strCancel));
4837 __dealloc(filename);
4838 }
4839 else goto not_handled;
4840 }
4841 break;
4842 case pccDocumentTop:
4843 if ( (document = current_document_x) != NULL )
4844 { if (document->origin_y > 0)
4845 { document->origin_y = 0; document->flags |= wvfScrollingUp;
4846 #if TGC_IS_CURSES
4847 document->aecur = INVALID_AE;
4848 #endif
4849 document_display(document, wrtRedraw);
4850 }
4851 }
4852 else goto not_handled;
4853 break;
4854 case pccDocumentBottom:
4855 document = current_document_x;
4856 if (document != NULL) document_display(document, wrtToEnd);
4857 else goto not_handled;
4858 break;
4859 case pccPageDown:
4860 document = current_document_x;
4861 if ( (document != NULL) && (document_section_height(document, &scrolloff)))
4862 { scroll_down:
4863 if (document->flags & wvfScreenFull)
4864 { document->origin_y += scrolloff; document->flags &= ~wvfScrollingUp;
4865 do_scroll:
4866 #if TGC_IS_CURSES
4867 document->aecur = INVALID_AE;
4868 #endif
4869 document_display(document, wrtRedraw);
4870 }
4871 }
4872 break;
4873 case pccPageUp:
4874 document = current_document_x;
4875 if ( (document != NULL) && (document_section_height(document, &scrolloff)))
4876 { tLinenumber y;
4877 negate_scroll_up: scrolloff = -scrolloff;
4878 scroll_up: y = document->origin_y + scrolloff;
4879 if (y < 0) y = 0;
4880 if (document->origin_y > y) /* actually need to scroll */
4881 { document->origin_y = y; document->flags |= wvfScrollingUp;
4882 goto do_scroll;
4883 }
4884 }
4885 break;
4886 case pccLineDown:
4887 if ( (document = current_document_x) != NULL )
4888 { scrolloff = 1; goto scroll_down; }
4889 break;
4890 case pccLineUp:
4891 if ( (document = current_document_x) != NULL )
4892 { scrolloff = -1; goto scroll_up; }
4893 break;
4894 case pccElementNext:
4895 if ( (document = current_document_x) != NULL )
4896 { tActiveElementNumber _ae = document->aecur, _ae2;
4897 if ( (_ae != INVALID_AE) &&
4898 ( (_ae2 = next_visible_ae(document, _ae)) != INVALID_AE ) )
4899 { activate_element(document, _ae2); }
4900 else if (document_section_height(document, &scrolloff))
4901 goto scroll_down;
4902 }
4903 break;
4904 case pccElementPrevious:
4905 if ( (document = current_document_x) != NULL )
4906 { tActiveElementNumber _ae = document->aecur, _ae2;
4907 if ( (_ae != INVALID_AE) &&
4908 ( (_ae2 = previous_visible_ae(document, _ae)) != INVALID_AE ) )
4909 { activate_element(document, _ae2); }
4910 else if (document_section_height(document, &scrolloff))
4911 goto negate_scroll_up;
4912 }
4913 break;
4914 case pccElementOpen: case pccWindowNewFromElement: case pccElementOpenSplit:
4915 { tActiveElementNumber _ae;
4916 tCantent* cantent;
4917 if ( ( (document = current_document_x) != NULL ) &&
4918 ( (_ae = document->aecur) != INVALID_AE ) &&
4919 ( (cantent = document->cantent) != NULL ) )
4920 { const tActiveElementBase* aebase = cantent->aebase;
4921 tActiveElementKind kind;
4922 tActiveElement* aes;
4923 if (aebase[_ae].flags & aefDisabled)
4924 { show_message(_("This element is disabled!"), truE); goto out; }
4925 javascript_handle_event(jekFocus, _ae);
4926 aes = document->active_element; kind = aebase[_ae].kind;
4927 switch (kind)
4928 {case aekLink:
4929 if ( ( (temp = aebase[_ae].data) != NULL) && (*temp != '\0') )
4930 { tWindow* destwin;
4931 tPrrFlags prrf = prrfRedrawOne;
4932 if ( (code == pccElementOpen) &&
4933 (is_browser_window(current_window_x)) )
4934 destwin = current_window_x;
4935 else
4936 { destwin = wk_browser_create();
4937 if (code == pccElementOpenSplit)
4938 { visible_window_x[1 - current_window_index_x] = destwin;
4939 prrf = prrfRedrawAll;
4940 }
4941 else current_window_x = destwin;
4942 }
4943 wk_browser_prr(destwin, temp, prrf, document);
4944 }
4945 break;
4946 case aekFormSubmit: case aekFormImage:
4947 goto handle_submit; /*@notreached@*/ break;
4948 case aekFormReset: goto handle_reset; /*@notreached@*/ break;
4949 case aekFormText:
4950 elig = eligFormText;
4951 start_input:
4952 if (aebase[_ae].flags & aefReadonly)
4953 { /* IMPLEMENTME: the user should be able to see the _whole_ contents
4954 of the element somehow, not only what is displayed within the
4955 normal web page layout! (E.g. the text might be clipped.) */
4956 readonly:
4957 show_message(_("This element is read-only!"), truE);
4958 goto out;
4959 }
4960 else
4961 { sprint_safe(strbuf3, "%s: ", _(strAek[kind]));
4962 __line_input_estart(elig, strbuf3, aes[_ae].current_text, NULL);
4963 /* CHECKME: respect the "maxlength" attribute for eligFormText and
4964 eligFormPassword? */
4965 }
4966 break;
4967 case aekFormPassword:
4968 elig = eligFormPassword; goto start_input; /*@notreached@*/ break;
4969 case aekFormFile:
4970 elig = eligFormFilename; goto start_input; /*@notreached@*/ break;
4971 case aekFormCheckbox:
4972 if (aebase[_ae].flags & aefReadonly) goto readonly;
4973 else
4974 { aes[_ae].flags ^= aefCheckedSelected;
4975 document_display(document, wrtRedraw); /* IMPROVEME? */
4976 }
4977 break;
4978 case aekFormRadio:
4979 if (aebase[_ae].flags & aefReadonly) goto readonly;
4980 else if (!(aes[_ae].flags & aefCheckedSelected))
4981 { const tHtmlFormNumber fn = calc_hfn(cantent, _ae);
4982 const char* name = aebase[_ae].data;
4983 aes[_ae].flags |= aefCheckedSelected;
4984 /* Uncheck all other radio buttons of the same name in this form */
4985 if ( (fn != INVALID_HTML_FORM_NUMBER) && (name != NULL) )
4986 { tActiveElementNumber a, a1 = cantent->form[fn].first_ae,
4987 a2 = cantent->form[fn].last_ae;
4988 if ( (a1 != INVALID_AE) && (a2 != INVALID_AE) )
4989 { for (a = a1; a <= a2; a++)
4990 { if ( (a != _ae) && (aebase[a].kind == aekFormRadio) )
4991 { const char* n = aebase[a].data;
4992 if ( (n != NULL) && (!my_strcasecmp(name, n)) )
4993 aes[a].flags &= ~aefCheckedSelected;
4994 }
4995 }
4996 }
4997 }
4998 document_display(document, wrtRedraw); /* IMPROVEME? */
4999 }
5000 break;
5001 case aekFormSelect:
5002 if (aebase[_ae].maxlength > 0)
5003 {
5004 #if CONFIG_MENUS & MENUS_HTML
5005 cm_setup_select_tag(aebase, document, _ae);
5006 #else
5007 menus_were_disabled();
5008 #endif
5009 }
5010 else show_message(_(strSelectionEmpty), truE);
5011 break;
5012 } /* switch */
5013 }
5014 }
5015 break;
5016 #if TGC_IS_GRAPHICS
5017 #define connect_sel(b, h, d) \
5018 connect_object(GTK_OBJECT(GTK_FILE_SELECTION(sel)->b), strGtkClicked, h, d)
5019 case pccLocalFileDirOpen:
5020 { GtkWidget* sel = gtk_file_selection_new(_("Open Local File/Directory"));
5021 connect_sel(ok_button, filesel_handle,
5022 filesel_data_create(sel, current_window));
5023 connect_sel(ok_button, graphics_window_destroy, sel);
5024 connect_sel(cancel_button, graphics_window_destroy, sel);
5025 show_widget(sel);
5026 }
5027 break;
5028 #endif
5029 case pccDump:
5030 #if CONFIG_EXTRA & EXTRA_DUMP
5031 if ( (document = current_document_x) != NULL )
5032 { line_input_estart(eligDumpFilename, _("Dump to: "), NULL, NULL,
5033 _("Dump to File"), _("Dump"), _(strCancel));
5034 }
5035 else goto not_handled;
5036 #else
5037 fwdact(_("Fdumps"));
5038 #endif
5039 break;
5040 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
5041 case pccDownload:
5042 dealloc(khmli_download_uri); khmli_download_referrer = NULL;
5043 line_input_estart(eligDownloadUri, _("Download from: "), NULL, NULL,
5044 _(strUcDownload), _("Proceed"), _(strCancel));
5045 break;
5046 case pccDownloadFromElement:
5047 { const tCantent* cantent;
5048 const tActiveElementBase* aebase;
5049 tActiveElementNumber _ae;
5050 if ( ( (document = current_document_x) != NULL ) &&
5051 #if TGC_IS_GRAPHICS
5052 ( (_ae = INVALID_AE) != INVALID_AE ) && /* FIXME! */
5053 #else
5054 ( (_ae = document->aecur) != INVALID_AE ) &&
5055 #endif
5056 ( (cantent = document->cantent) != NULL ) &&
5057 ( (aebase = cantent->aebase) != NULL ) )
5058 { const tActiveElementKind aek = aebase[_ae].kind;
5059 if (aek == aekLink)
5060 { temp = aebase[_ae].data;
5061 if ( (temp != NULL) && (*temp != '\0') )
5062 { my_strdedup(khmli_download_uri, temp);
5063 khmli_download_referrer = document;
5064 ask_download_to();
5065 }
5066 }
5067 else show_message(_("That's not a link"), truE);
5068 }
5069 else goto not_handled;
5070 }
5071 break;
5072 #else
5073 case pccDownload: case pccDownloadFromElement:
5074 fwdact(_("Fdownloads")); break;
5075 #endif
5076 case pccDocumentEnforceHtml:
5077 { if ( (document = current_document_x) != NULL )
5078 { if (config.flags & cfDontConfirmHtml) interpret_as_html();
5079 else ask_yesno(_("Really interpret as HTML?"), cgHtml, NULL);
5080 }
5081 else goto not_handled;
5082 }
5083 break;
5084 case pccFormReset:
5085 handle_reset:
5086 /* CHECKME: only ask if actually inside a form, otherwise fail! */
5087 if (config.flags & cfDontConfirmReset) current_document_form_reset();
5088 else ask_yesno(_("Really reset this form?"), cgReset, NULL);
5089 break;
5090 case pccFormSubmit:
5091 handle_submit:
5092 if (config.flags & cfDontConfirmSubmit) current_document_form_submit();
5093 else if ( (document = current_document_x) != NULL )
5094 { const tCantent* cantent = document->cantent;
5095 const tHtmlFormNumber hfn = calc_hfn(cantent, document->aecur);
5096 const tHtmlForm* form;
5097 char* spfbuf;
5098 if ( (cantent == NULL) || (hfn == INVALID_HTML_FORM_NUMBER) )
5099 { cnsniaf(); goto out; }
5100 form = &(cantent->form[hfn]);
5101 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
5102 _("Really submit this form to \"%s\" (%s)?"), form->action_uri,
5103 ( (form->flags & hffMethodPost) ? strPost : strGet ));
5104 ask_yesno(spfbuf, cgSubmit, NULL); my_spf_cleanup(strbuf, spfbuf);
5105 }
5106 else goto not_handled;
5107 break;
5108 case pccStop:
5109 { tResourceRequest* request;
5110 document = current_document_x;
5111 if ( (document != NULL) && ( (request = document->request) != NULL) )
5112 resource_request_stop(request);
5113 else goto not_handled;
5114 }
5115 break;
5116 case pccDocumentSearch:
5117 elig = eligDocumentSearch; msg = _("Search string: ");
5118 check_search:
5119 if ( (document = current_document_x) != NULL )
5120 { line_input_estart(elig, msg, search_string, NULL, _("Search String"),
5121 _(strSearch), _(strCancel));
5122 }
5123 else goto not_handled;
5124 break;
5125 case pccDocumentSearchBackward:
5126 elig = eligDocumentSearchBackward; msg = _("Search string backwards: ");
5127 goto check_search; /*@notreached@*/ break;
5128 case pccDocumentSearchNext:
5129 if ( (search_string != NULL) && ( (document = current_document_x) != NULL))
5130 document_display(document, wrtSearch);
5131 break;
5132 case pccDocumentSearchPrevious:
5133 if ( (search_string != NULL) && ( (document = current_document_x) != NULL))
5134 document_display(document, wrtSearchBackward);
5135 break;
5136 case pccElementEnable:
5137 { tActiveElementNumber _ae;
5138 const tCantent* cantent;
5139 document = current_document_x;
5140 if ( (document != NULL) && ( (_ae = document->aecur) != INVALID_AE ) &&
5141 ( (cantent = document->cantent) != NULL ) &&
5142 (cantent->aebase[_ae].flags & aefDisabled) )
5143 { if (config.flags & cfDontConfirmEnable)
5144 cantent->aebase[_ae].flags &= ~aefDisabled;
5145 else ask_yesno(_("Really enable this element?"), cgEnable, NULL);
5146 }
5147 else goto not_handled;
5148 }
5149 break;
5150 case pccElementInfo:
5151 { tActiveElementNumber _ae;
5152 tCantent* cantent;
5153 document = current_document_x;
5154 if ( (document != NULL) && ( (_ae = document->aecur) != INVALID_AE ) &&
5155 ( (cantent = document->cantent) != NULL ) )
5156 { const tActiveElementBase* aeb = &(cantent->aebase[_ae]);
5157 const tActiveElementFlags flags = aeb->flags;
5158 const char *dat = null2empty(aeb->data),
5159 *disabl = ( (flags & aefDisabled) ? (_("disabled ")) : strEmpty ),
5160 *readonl = ( (flags & aefReadonly) ? (_("read-only ")) : strEmpty ),
5161 *mult = ( (flags & aefMultiple) ? (_("multi-option ")) : strEmpty );
5162 char* spfbuf;
5163 my_spf(strbuf, STRBUF_SIZE, &spfbuf, "%s%s%s%s%s%s", disabl, readonl,
5164 mult, _(strAek[aeb->kind]), ((*dat != '\0') ? ", " : strEmpty), dat);
5165 show_message(spfbuf, falsE); my_spf_cleanup(strbuf, spfbuf);
5166 }
5167 else goto not_handled;
5168 }
5169 break;
5170 #if OPTION_EXECEXT & EXECEXT_SHELL
5171 case pccExecextShell:
5172 if ( (config.flags & cfExecextShellCustom) &&
5173 (config.execext_shell == NULL) )
5174 { show_message(_(strResourceError[reConfigForbids]), truE); }
5175 else
5176 { line_input_estart(eligExecextShell, _("Execute shell command: "), NULL,
5177 NULL, _("Execute Shell Command"), _("Execute"), _(strCancel));
5178 }
5179 break;
5180 case pccExecextShellFlip:
5181 config.flags ^= cfExecextShellCustom;
5182 sprint_safe(strbuf, _("execext-shell configuration: %s"), (config.flags &
5183 cfExecextShellCustom) ? _("customized") : _("standard"));
5184 show_message(strbuf, falsE);
5185 break;
5186 #else
5187 case pccExecextShell: case pccExecextShellFlip:
5188 fwdact(_(strFexecext)); break;
5189 #endif
5190 #if CONFIG_SESSIONS
5191 case pccSessionSave:
5192 line_input_estart(eligSessionSave, _("Save session as: "),
5193 config.session_default, NULL, _("Save Session"), _(strSave),
5194 _(strCancel));
5195 break;
5196 case pccSessionResume:
5197 line_input_estart(eligSessionResume, _("Resume session from: "),
5198 config.session_default, NULL, _("Resume Session"), _("Resume"),
5199 _(strCancel));
5200 break;
5201 #else
5202 case pccSessionSave: case pccSessionResume: fwdact(_(strFsessions)); break;
5203 #endif
5204 case pccMouseFlip: case pccMouseOff: case pccMouseOn:
5205 mouse_flip(code - pccMouseFlip); break;
5206 case pccScrollBarsFlip:
5207 #if MIGHT_USE_SCROLL_BARS
5208 config.flags ^= cfUseScrollBars; window_redraw_all();
5209 #else
5210 fwdact(_("Fscroll bars"));
5211 #endif
5212 break;
5213 case pccQuit:
5214 if (config.flags & cfDontConfirmQuit) do_quit();
5215 else ask_yesno(_("Really quit retawq?"), cgQuit, NULL);
5216 break;
5217 default: not_handled: retval = falsE; break; /* command not handled */
5218 }
5219 out:
5220 return(retval);
5221 }
5222
handle_command_code(tProgramCommandCode pcc)5223 static tBoolean handle_command_code(tProgramCommandCode pcc)
5224 { tBoolean retval;
5225 tWindow* window = current_window_x;
5226 tBoolean (*func)(tWindow*, tProgramCommandCode) = window->spec->handle_pcc;
5227 retval = ( (func != NULL) ? ((func)(window, pcc)) : falsE );
5228 if (!retval) retval = generic_handle_command(pcc);
5229 return(retval);
5230 }
5231
5232 #if TGC_IS_CURSES
5233
5234 #if CONFIG_DO_TEXTMODEMOUSE
handle_key_mouse(void)5235 static one_caller void handle_key_mouse(void)
5236 { MEVENT event;
5237 short x, _y;
5238 tLinenumber y, yt;
5239 tVisibleWindowIndex i;
5240 tWindow* window;
5241 tBrowserDocument* document;
5242 const tActiveElement* aes;
5243 tActiveElementNumber _ae;
5244 mmask_t mask;
5245 if (getmouse(&event) != OK) return;
5246 mask = event.bstate;
5247 if (!(mask & TEXTMODEMOUSE_MASK)) return; /* "should not happen" */
5248 x = event.x; _y = event.y; y = (tLinenumber) _y;
5249 if (!is_bottom_occupied)
5250 { sprint_safe(strbuf, _("mouse click: %d, %d"), x, _y);
5251 show_message(strbuf, falsE);
5252 }
5253 if (key_handling_mode == khmLineInput)
5254 { if ( (lid.curr == 1) && (y == lid_area(row)) && (x >= lid_area(colmin)) &&
5255 (x <= lid_area(colmax)) )
5256 { short offset = x - lid_area(colcurr);
5257 if (offset < 0)
5258 { li_off:
5259 lid_area(colcurr) += offset; lid_area(pos) += offset;
5260 (void) move(lid_area(row), lid_area(colcurr));
5261 }
5262 else if (offset > 0)
5263 { short pos = lid_area(pos) + offset;
5264 if (pos > lid_area(len)) pos = lid_area(len);
5265 if (pos > lid_area(pos)) { offset = pos - lid_area(pos); goto li_off; }
5266 }
5267 /* "else": offset == 0, nothing changes */
5268 }
5269 return;
5270 }
5271 #if CONFIG_MENUS
5272 else if (key_handling_mode == khmMenu)
5273 { if (mask & BUTTON1_CLICKED) cm_handle_mouseclick(x, _y);
5274 return;
5275 }
5276 #endif
5277 if (y > LINES - 2)
5278 { check_other_buttons:
5279 _ae = INVALID_AE;
5280 do_check_other_buttons:
5281 if (mask & (BUTTON2_CLICKED | BUTTON3_CLICKED))
5282 {
5283 #if CONFIG_MENUS & MENUS_CONTEXT
5284 cm_setup_contextual(x, _y, _ae);
5285 #else
5286 menus_were_disabled();
5287 #endif
5288 }
5289 return;
5290 }
5291 if (visible_window_x[1] == NULL) i = 0;
5292 else
5293 { if (y == VMIDDLE) goto check_other_buttons;
5294 else if (y < VMIDDLE) i = 0;
5295 else { i = 1; y -= (VMIDDLE + 1); }
5296 }
5297 current_window_index_x = i; window = current_window_x;
5298 document = current_document_x;
5299 if ( (document == NULL) || (document->aenum <= 0) ) goto check_other_buttons;
5300 yt = y + document->origin_y; aes = document->active_element;
5301 for (_ae = 0; _ae < document->aenum; _ae++)
5302 { const tActiveElementCoordinates* aec = aes[_ae].aec;
5303 while (aec != NULL)
5304 { tLinenumber yc = aec->y;
5305 short x1 = aec->x1, x2 = aec->x2;
5306 if ( (yt == yc) && (x >= x1) && (x <= x2) )
5307 { const tActiveElementBase* aeb;
5308 if (_ae != document->aecur) activate_element(document, _ae);
5309 if (!(mask & BUTTON1_CLICKED)) goto do_check_other_buttons;
5310 aeb = &(document->cantent->aebase[_ae]);
5311 if (aeb->kind == aekLink)
5312 { javascript_handle_event(jekClick, _ae);
5313 wk_browser_prr(require_browser_window(), aeb->data, prrfRedrawOne,
5314 document);
5315 }
5316 return; /* done */
5317 }
5318 aec = aec->next;
5319 }
5320 }
5321 goto check_other_buttons;
5322 }
5323 #endif /* #if CONFIG_DO_TEXTMODEMOUSE */
5324
lookup_command_key(tKey key)5325 static one_caller tMbsIndex lookup_command_key(tKey key)
5326 { if (keymap_command_keys_num <= 0) return(INVALID_INDEX); /* "can't happen" */
5327 my_binary_search(0, keymap_command_keys_num - 1,
5328 my_numcmp(key, keymap_command_keys[idx].key), return(idx))
5329 }
5330
handle_key(tKey key)5331 static void handle_key(tKey key)
5332 {
5333 #if CONFIG_DO_TEXTMODEMOUSE
5334 if (key == KEY_MOUSE) { handle_key_mouse(); return; }
5335 #endif
5336 #if (TGC_IS_CURSES) && (defined(KEY_RESIZE))
5337 if (key == KEY_RESIZE)
5338 { /* might happen if the curses library installed its own resize signal
5339 handler and our MIGHT_SIG_TERMSIZE was 0 */
5340 if (key_handling_mode == khmLineInput) line_input_resize();
5341 #if CONFIG_MENUS
5342 else if (key_handling_mode == khmMenu) cm_cancel();
5343 #endif
5344 window_redraw_all();
5345 return;
5346 }
5347 #endif
5348
5349 remap_key(&key);
5350 if (is_khm_command)
5351 { if (key != KEY_CANCEL)
5352 { tWindow* window = current_window_x;
5353 const tWindowSpec* spec = window->spec;
5354 tBoolean (*func)(tWindow*, tKey) = spec->handle_key;
5355 tBoolean might_try_key = cond2boolean(func != NULL);
5356 tMbsIndex idx;
5357 if ( (spec->flags & wsfWantKeyFirst) && (might_try_key) )
5358 { if ((func)(window, key)) return; /* done */
5359 else might_try_key = falsE; /* don't try again */
5360 }
5361 idx = lookup_command_key(key);
5362 if (idx >= 0)
5363 { if (handle_command_code(keymap_command_keys[idx].pcc)) return; }
5364 else if ( (might_try_key) && ((func)(window, key)) ) return; /* done */
5365 show_message( ( (idx >= 0) ? _("Keyboard command not handled") :
5366 _("Key not handled") ), truE);
5367 }
5368 }
5369 #if TGC_IS_CURSES
5370 else if (key_handling_mode == khmLineInput) line_input_handle_key(key);
5371 #if CONFIG_MENUS
5372 else if (key_handling_mode == khmMenu)
5373 { switch (key)
5374 { case KEY_ENTER: cm_apply(); break;
5375 case KEY_CANCEL: cm_cancel(); break;
5376 case KEY_UP: cm_line_up(); break;
5377 case KEY_DOWN: cm_line_down(); break;
5378 case KEY_PPAGE: cm_page_up(); break;
5379 case KEY_NPAGE: cm_page_down(); break;
5380 }
5381 }
5382 #endif
5383 #endif /* #if TGC_IS_CURSES */
5384 }
5385
5386 #endif /* #if TGC_IS_CURSES */
5387
rwd_cb_redir(const tRemainingWork * rw)5388 static tBoolean rwd_cb_redir(const tRemainingWork* rw)
5389 { /* IMPLEMENTME: with scripting, we must make sure that the window and
5390 document aren't deallocated before we get here - use dhm! */
5391 tWindow* window = (tWindow*) (rw->data1);
5392 const tBrowserDocument* referrer = (const tBrowserDocument*) (rw->data2);
5393 const char* uri = (const char*) (rw->data3);
5394 wk_browser_prr(window, uri, prrfRedrawOne | prrfIsHttpRedirection, referrer);
5395 memory_deallocate(uri); return(falsE);
5396 }
5397
test_redirection(tWindow * window,tBrowserDocument * document)5398 static void test_redirection(tWindow* window, tBrowserDocument* document)
5399 { const tResourceRequest* request;
5400 const tResource* resource;
5401 const char* redirection;
5402 tCantent* cantent;
5403 if ( (document->flags & wvfHandledRedirection) ||
5404 ( (cantent = document->cantent) == NULL ) ||
5405 ( (redirection = cantent->redirection) == NULL ) )
5406 return;
5407 document->flags |= wvfHandledRedirection;
5408 if (document->bddm == bddmSource) return; /* don't redirect */
5409 request = document->request;
5410 resource = ( (request != NULL) ? (request->resource) : NULL );
5411 if ( (resource != NULL) && (is_httplike(resource->protocol)) )
5412 { const tServerStatusCode ssc = resource->server_status_code;
5413 if ( (ssc == 301) || (ssc == 302) || (ssc == 303) || (ssc == 307) )
5414 { tRemainingWork* rw;
5415 setup_redirect: debugmsg("redirecting\n");
5416 rw = remaining_work_create(rwd_cb_redir); rw->data1 = window;
5417 rw->data2 = document; rw->data3 = my_strdup(redirection);
5418 }
5419 }
5420 else if (cantent->caf & cafHtmlRedirection) goto setup_redirect;
5421 }
5422
5423
5424 /** Custom connections */
5425
5426 #if CONFIG_CUSTOM_CONN
5427
5428 static const char strAccount[] = "account", strAppend[] = "append",
5429 strAscii[] = "ascii", strAuth[] = "auth", strBinary[] = "binary",
5430 strCdup[] = "cdup", strDelete[] = "delete", strDownload[] = "download",
5431 strLcGet[] = "get", strHash[] = "hash", strLabel[] = "label",
5432 strLcd[] = "lcd", strLs[] = "ls", strMkdir[] = "mkdir", strOpen[] = "open",
5433 strPass[] = "pass", strPut[] = "put", strPwd[] = "pwd", strQuote[] = "quote",
5434 strRestart[] = "restart", strRmdir[] = "rmdir", strSite[] = "site",
5435 strTask[] = "task", strUpload[] = "upload", strUser[] = "user",
5436 strExcl[] = "!", strSpaceOpeningBrace[] = " (", strClosingBrace[] = ")",
5437 strTooFewArguments[] = N_("Too few arguments\n");
5438 #define strCd (strLcd + 1)
5439
5440 static int cc_hash_bytes = 1024; /* ("int" simply for my_atoi() compliance) */
5441 static tBoolean cc_do_hash = falsE;
5442
5443 #if CONFIG_CONSOLE
5444 #define cc_on_console (program_mode == pmConsole)
5445 #else
5446 #define cc_on_console (falsE)
5447 #endif
5448
5449 my_enum1 enum
5450 { cccfNone = 0, cccfConsoleOnly = 0x01, cccfFtpOnly = 0x02, cccfDoQuote = 0x04,
5451 cccfDontReqConn = 0x08
5452 } my_enum2(unsigned char) tCccFlags;
5453
5454 my_enum1 enum
5455 { ccccUnknown = -2, ccccAmbiguous = -1, ccccQuit = 0, ccccVersion = 1,
5456 ccccHelp = 2, ccccHash = 3, ccccLcd = 4, ccccTask = 5, ccccInfo = 6,
5457 ccccLabel = 7, ccccAuth = 8, ccccDownload = 9, ccccUpload = 10,
5458 ccccOpen = 11, ccccClose = 12, ccccQuote = 13, ccccUser = 14, ccccPass = 15,
5459 ccccAccount = 16, ccccCd = 17, ccccCdup = 18, ccccPwd = 19, ccccLs = 20,
5460 ccccAscii = 21, ccccBinary = 22, ccccDelete = 23, ccccMkdir = 24,
5461 ccccRmdir = 25, ccccRestart = 26, ccccGet = 27, ccccPut = 28,
5462 ccccAppend = 29, ccccSite = 30
5463 #if OPTION_EXECEXT & EXECEXT_SHELL
5464 , ccccShell = 31
5465 #endif
5466 } my_enum2(signed char) tCccCode; /* "custom connection command code" :-) */
5467 #define MAX_CCCC (31)
5468 #define cccc_is_valid(cccc) ((cccc) >= 0)
5469
5470 typedef unsigned char tCccArgnum; /* roughly 0..100 */
5471
5472 typedef struct
5473 { const char *default_cmdstr, *helpstr;
5474 tCccFlags flags;
5475 tCccArgnum minarg, maxarg;
5476 } tCccData;
5477
5478 static const tCccData cccdata[MAX_CCCC + 1] =
5479 { /* ccccQuit */ { strQuit, N_("quit retawq"), cccfConsoleOnly |
5480 cccfDontReqConn, 0, 0 },
5481 /* ccccVersion */ { strVersion, N_("print version information"),
5482 cccfDontReqConn, 0, 0 },
5483 /* ccccHelp */ { strHelp, N_("print help information"), cccfDontReqConn, 0,
5484 100 },
5485 /* ccccHash */ { strHash, N_("hashmarks on/off"), cccfDontReqConn, 0, 1 },
5486 /* ccccLcd */ { strLcd, N_("change local directory"), cccfConsoleOnly |
5487 cccfDontReqConn, 0, 1},
5488 /* ccccTask */ { strTask, N_("switch to <task-id>"), cccfConsoleOnly |
5489 cccfDontReqConn, 1, 1 },
5490 /* ccccInfo */ { strInfo, N_("show information about existing task(s)"),
5491 cccfConsoleOnly | cccfDontReqConn, 0, 100 },
5492 /* ccccLabel */ { strLabel, N_("label a task"), cccfConsoleOnly |
5493 cccfDontReqConn, 1, 2 },
5494 /* ccccAuth */ { strAuth, N_("start TLS/SSL handshake (p/c/s)"), cccfFtpOnly,
5495 0, 1 },
5496 /* ccccDownload */ { strDownload, N_("download via URL"), cccfConsoleOnly |
5497 cccfDontReqConn, 1, 2 },
5498 /* ccccUpload */ { strUpload, N_("upload via URL"), cccfConsoleOnly |
5499 cccfDontReqConn, 1, 2 },
5500 /* ccccOpen */ { strOpen, N_("open a connection to a server (via URL)"),
5501 cccfDontReqConn, 1, 1 },
5502 /* ccccClose */ { strClose, N_("close some connection(s)"),
5503 cccfDontReqConn, 0, 100 },
5504 /* ccccQuote */ { strQuote, N_("send a verbatim command"), cccfFtpOnly, 1,
5505 100 },
5506 /* ccccUser */ { strUser, N_("send username"), cccfFtpOnly, 0, 1 },
5507 /* ccccPass */ { strPass, N_("send password"), cccfFtpOnly, 0, 1 },
5508 /* ccccAccount */ { strAccount, N_("send account"), cccfFtpOnly, 1, 1 },
5509 /* ccccCd */ { strCd, N_("change remote directory"), cccfFtpOnly, 1, 1 },
5510 /* ccccCdup */ { strCdup, N_("change to remote parent directory"),
5511 cccfFtpOnly | cccfDoQuote, 0, 0 },
5512 /* ccccPwd */ { strPwd, N_("print remote working directory"), cccfFtpOnly |
5513 cccfDoQuote, 0, 0 },
5514 /* ccccLs */ { strLs, N_("list contents of remote directory"), cccfNone,
5515 0, 100 },
5516 /* ccccAscii */ { strAscii, N_("set file transfer type to ASCII"),
5517 cccfFtpOnly, 0, 0 },
5518 /* ccccBinary */ { strBinary, N_("set file transfer type to binary"),
5519 cccfFtpOnly, 0, 0 },
5520 /* ccccDelete */ { strDelete, N_("delete remote file"), cccfFtpOnly, 1, 1 },
5521 /* ccccMkdir */ { strMkdir, N_("make remote directory"), cccfFtpOnly, 1, 1 },
5522 /* ccccRmdir */ { strRmdir, N_("delete remote directory"), cccfFtpOnly, 1,1},
5523 /* ccccRestart */ { strRestart, N_("set file transfer restart marker"),
5524 cccfFtpOnly, 1, 1 },
5525 /* ccccGet */ { strLcGet, N_("receive one file"), cccfNone, 1, 2 },
5526 /* ccccPut */ { strPut, N_("upload one file"), cccfNone, 1, 2 },
5527 /* ccccAppend */ { strAppend, N_("append to remote file"), cccfNone, 1, 2 },
5528 /* ccccSite */ { strSite, N_("send site-specific command"), cccfFtpOnly |
5529 cccfDoQuote, 1, 100 },
5530 /* ccccShell */ { strExcl, N_("execute a shell command"), cccfConsoleOnly |
5531 cccfDontReqConn, 1, 100 }
5532 };
5533
5534 typedef struct
5535 { const char* cmdstr; /* (sorted in strcmp() order) */
5536 tCccCode code;
5537 } tCccXlat; /* "custom connection command translation" */
5538
5539 static const tCccXlat cccxlat[] =
5540 {
5541 #if OPTION_EXECEXT & EXECEXT_SHELL
5542 { strExcl, ccccShell },
5543 #endif
5544 { strQm, ccccHelp }, /* abbr. alias for lazy users */
5545 { strAccount, ccccAccount },
5546 /* { strAppend, ccccAppend }, */
5547 { strAscii, ccccAscii },
5548 #if OPTION_TLS
5549 { strAuth, ccccAuth },
5550 #endif
5551 { strBinary, ccccBinary },
5552 { "bye", ccccQuit }, /* alias in "usual text console FTP clients" */
5553 /* { strCat, ccccCat }, -- print contents of remote file */
5554 { strCd, ccccCd },
5555 { strCdup, ccccCdup },
5556 { strClose, ccccClose },
5557 { strDelete, ccccDelete },
5558 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
5559 { strDownload, ccccDownload },
5560 #endif
5561 { "exit", ccccQuit }, /* alias in "usual text console FTP clients" */
5562 { strLcGet, ccccGet },
5563 { strHash, ccccHash },
5564 { strHelp, ccccHelp },
5565 { strInfo, ccccInfo },
5566 { strLabel, ccccLabel },
5567 { strLcd, ccccLcd },
5568 { strLs, ccccLs },
5569 { strMkdir, ccccMkdir },
5570 { strOpen, ccccOpen },
5571 { strPass, ccccPass },
5572 /* { strPut, ccccPut }, */
5573 { strPwd, ccccPwd },
5574 { strQuit, ccccQuit },
5575 { strQuote, ccccQuote },
5576 { strRestart, ccccRestart },
5577 { strRmdir, ccccRmdir },
5578 { strSite, ccccSite },
5579 /* { strStop, ccccStop }, -- e.g. stop sequencer and/or close dconn */
5580 { strTask, ccccTask },
5581 /* { strUpload, ccccUpload }, */
5582 { strUser, ccccUser },
5583 { strVersion, ccccVersion }
5584 };
5585
5586 #define MAXCMDSTRLEN (8) /* length of the command string "download" */
5587
5588 struct tCustomConnContext;
5589 struct tCcSeqEntry;
5590
5591 my_enum1 enum
5592 { ccssUnknown = 0, ccssSend = 1, ccssFinalize = 2, ccssCancel = 3
5593 } my_enum2(unsigned char) tCcSeqStep;
5594
5595 typedef void (*tCcSeqCallback)(const struct tCustomConnContext*,
5596 /*@null@*/ tResource* resource, struct tCcSeqEntry* entry, tCcSeqStep);
5597
5598 typedef struct tCcSeqEntry
5599 { /* struct tCcSeqEntry* next; */
5600 tCcSeqCallback callback;
5601 void* data; /* callback-specific information, e.g. an FTP command string */
5602 tBoolean did_send;
5603 } tCcSeqEntry; /* "custom connection sequencer entry" */
5604
5605 typedef struct
5606 { tBoolean (*may_handle)(const struct tCustomConnContext*);
5607 tBoolean (*handle_cccc)(const struct tCustomConnContext*, tCccCode,
5608 const char**, tCccArgnum);
5609 tBoolean (*try_open)(const struct tCustomConnContext*);
5610 void (*do_open)(const struct tCustomConnContext*, tResourceRequest*);
5611 void (*do_start)(const struct tCustomConnContext*, unsigned char,
5612 const void*);
5613 void (*do_prepare_sequencer)(const struct tCustomConnContext*, const char*,
5614 tCcSeqCallback);
5615 void (*getput)(const struct tCustomConnContext*, unsigned char, const char*,
5616 const char*);
5617 } tCustomConnOps;
5618
5619 typedef struct tCustomConnContext
5620 { const tCustomConnOps* ops;
5621 void* data; /* e.g. console task ID number */
5622 } tCustomConnContext;
5623
__ccc_do_lookup(const char * str)5624 static one_caller tMbsIndex __ccc_do_lookup(const char* str)
5625 { my_binary_search(0, ARRAY_ELEMNUM(cccxlat) - 1,
5626 strcmp(str, cccxlat[idx].cmdstr), return(idx))
5627 }
5628
5629 #define ccc_xlat(idx) ( ((idx) >= 0) ? cccxlat[idx].code : ccccUnknown )
5630
__ccc_lookup(const char * str)5631 static one_caller tCccCode __ccc_lookup(const char* str)
5632 { const tMbsIndex idx = __ccc_do_lookup(str);
5633 return(ccc_xlat(idx));
5634 }
5635
__ccc_do_lookup_prefix(const char * prefix,size_t prefixlen)5636 static one_caller tMbsIndex __ccc_do_lookup_prefix(const char* prefix,
5637 size_t prefixlen)
5638 { my_binary_search(0, ARRAY_ELEMNUM(cccxlat) - 1,
5639 strncmp(prefix, cccxlat[idx].cmdstr, prefixlen), return(idx))
5640 }
5641
5642 static tMbsIndex ccc_minidx, ccc_maxidx, /* for use with ccccAmbiguous */
5643 ccc_abbridx; /* for abbreviated commands */
5644
__ccc_lookup_prefix(const char * prefix)5645 static one_caller tCccCode __ccc_lookup_prefix(const char* prefix)
5646 { const size_t prefixlen = strlen(prefix);
5647 const tMbsIndex idx = __ccc_do_lookup_prefix(prefix, prefixlen);
5648 if (idx >= 0) /* got a "candidate"; check ambiguity */
5649 { tMbsIndex minidx, maxidx;
5650 minidx = maxidx = idx;
5651 while ( (--minidx >= 0) &&
5652 (!strncmp(prefix, cccxlat[minidx].cmdstr, prefixlen)) )
5653 { /* loop */ }
5654 while ( (++maxidx < (tMbsIndex) (ARRAY_ELEMNUM(cccxlat))) &&
5655 (!strncmp(prefix, cccxlat[maxidx].cmdstr, prefixlen)) )
5656 { /* loop */ }
5657 minidx++; maxidx--;
5658 if (minidx < maxidx)
5659 { ccc_minidx = minidx; ccc_maxidx = maxidx; return(ccccAmbiguous); }
5660 else ccc_abbridx = idx;
5661 }
5662 return(ccc_xlat(idx));
5663 }
5664
ccc_lookup(const char * str)5665 static tCccCode ccc_lookup(const char* str)
5666 { tCccCode cccc = __ccc_lookup(str);
5667 ccc_abbridx = INVALID_INDEX;
5668 if (cccc == ccccUnknown) cccc = __ccc_lookup_prefix(str);
5669 return(cccc);
5670 }
5671
5672 static char* ccs_ptr;
5673 static tBoolean ccs_is_first;
5674
ccs_append(const char * str)5675 static void ccs_append(const char* str)
5676 { char ch;
5677 if (ccs_is_first) ccs_is_first = falsE;
5678 else *ccs_ptr++ = ' ';
5679 while ( (ch = *str++) != '\0' ) *ccs_ptr++ = ch;
5680 }
5681
cc_concat(const char * cmd,const char ** arg,tCccArgnum argnum,tBoolean append_crlf)5682 static void cc_concat(const char* cmd, const char** arg, tCccArgnum argnum,
5683 tBoolean append_crlf)
5684 { tCccArgnum i;
5685 ccs_ptr = strbuf; ccs_is_first = truE;
5686 if (cmd != NULL) ccs_append(cmd);
5687 for (i = 0; i < argnum; i++) ccs_append(arg[i]);
5688 if (append_crlf) { *ccs_ptr++ = '\r'; *ccs_ptr++ = '\n'; }
5689 *ccs_ptr = '\0';
5690 }
5691
cc_do_start(const tCustomConnContext * context,unsigned char what,const void * whatever)5692 static __my_inline void cc_do_start(const tCustomConnContext* context,
5693 unsigned char what, const void* whatever)
5694 { (context->ops->do_start)(context, what, whatever);
5695 }
5696
cc_do_start_cmd(const tCustomConnContext * context,const char * cmd)5697 static __my_inline void cc_do_start_cmd(const tCustomConnContext* context,
5698 const char* cmd)
5699 { cc_do_start(context, 0, cmd);
5700 }
5701
cc_start_cmd(const tCustomConnContext * context,const char * cmd,const char ** arg,tCccArgnum argnum)5702 static __my_inline void cc_start_cmd(const tCustomConnContext* context,
5703 const char* cmd, const char** arg, tCccArgnum argnum)
5704 { cc_concat(cmd, arg, argnum, truE); cc_do_start_cmd(context, strbuf);
5705 }
5706
cc_start_rch(const tCustomConnContext * context,tResourceCommandHandshake rch)5707 static __my_inline void cc_start_rch(const tCustomConnContext* context,
5708 tResourceCommandHandshake rch)
5709 { cc_do_start(context, 1, MY_INT_TO_POINTER(rch));
5710 }
5711
5712 #if OPTION_TLS
cc_start_auth(const tCustomConnContext * context,tFtpTlsMethod ftm)5713 static __my_inline void cc_start_auth(const tCustomConnContext* context,
5714 tFtpTlsMethod ftm)
5715 { cc_do_start(context, 2, MY_INT_TO_POINTER(ftm));
5716 }
5717 #endif
5718
cc_sequencer_prepare(const tCustomConnContext * context,const char * cmd,tCcSeqCallback callback)5719 static __my_inline void cc_sequencer_prepare(const tCustomConnContext* context,
5720 const char* cmd, tCcSeqCallback callback)
5721 { (context->ops->do_prepare_sequencer)(context, cmd, callback);
5722 }
5723
cc_sequencer_cleanup(const tCustomConnContext * context,tCcSeqEntry * entry,tResource * resource)5724 static void cc_sequencer_cleanup(const tCustomConnContext* context,
5725 tCcSeqEntry* entry, tResource* resource)
5726 { tCcSeqCallback callback = entry->callback;
5727 if (callback != NULL)
5728 { (callback)(context, resource, entry, (entry->did_send) ? ccssFinalize :
5729 ccssCancel);
5730 my_memclr(entry, sizeof(tCcSeqEntry));
5731 }
5732 }
5733
cc_seq_cb_ls(const tCustomConnContext * context,tResource * resource,tCcSeqEntry * entry,tCcSeqStep step)5734 static void cc_seq_cb_ls(const tCustomConnContext* context,
5735 /*@null@*/ tResource* resource, tCcSeqEntry* entry, tCcSeqStep step)
5736 { const char* cmd;
5737 switch (step)
5738 { case ccssSend:
5739 if (resource != NULL) resource->flags |= rfCustomConnBd1;
5740 cmd = (const char*) entry->data;
5741 cc_do_start_cmd(context, cmd); memory_deallocate(cmd);
5742 entry->data = NULL; break;
5743 case ccssFinalize: case ccssCancel:
5744 if (resource != NULL) resource->flags &= ~rfCustomConnBdAny;
5745 __dealloc(entry->data); break;
5746 }
5747 }
5748
cc_seq_cb_retr(const tCustomConnContext * context,tResource * resource,tCcSeqEntry * entry,tCcSeqStep step)5749 static void cc_seq_cb_retr(const tCustomConnContext* context,
5750 /*@null@*/ tResource* resource, tCcSeqEntry* entry, tCcSeqStep step)
5751 { const char* cmd;
5752 switch (step)
5753 { case ccssSend:
5754 if (resource != NULL) resource->flags |= rfDownload | rfCustomConnBd4;
5755 cmd = (const char*) entry->data;
5756 cc_do_start_cmd(context, cmd); memory_deallocate(cmd);
5757 entry->data = NULL; break;
5758 case ccssFinalize: case ccssCancel:
5759 if (resource != NULL)
5760 { sinking_data_deallocate(&(resource->sinking_data));
5761 resource->flags &= ~(rfDownload | rfCustomConnBdAny);
5762 }
5763 __dealloc(entry->data); break;
5764 }
5765 }
5766
ccc_execute(const tCustomConnContext * context,char * inputstr)5767 static one_caller void ccc_execute(const tCustomConnContext* context,
5768 char* inputstr)
5769 /* IMPLEMENTME further! */
5770 { static const char strCommaSpace[] = ", ";
5771 tCccCode cccc;
5772 const tCccData* data;
5773 tCccFlags cccf;
5774 enum { MAXNUM = 50 };
5775 const char *cmdstr, *_arg[MAXNUM], **arg, *sendcmd, *filename;
5776 char type_ch, numbuf[200], *temp = inputstr;
5777 tCccArgnum argnum = 0, i;
5778 #if OPTION_TLS
5779 tFtpTlsMethod ftm;
5780 #endif
5781 #if CONFIG_DEBUG
5782 sprint_safe(debugstrbuf, "ccc_execute(): data=%p, cmd=%s\n", context->data,
5783 inputstr);
5784 debugmsg(debugstrbuf);
5785 #endif
5786
5787 /* split arguments */
5788 splitloop:
5789 while ( (*temp == ' ') || (*temp == '\t') ) *temp++ = '\0';
5790 if (*temp != '\0')
5791 { if (argnum >= MAXNUM)
5792 { err_tma: cc_output_errstr(_("Too many arguments\n")); return; }
5793 _arg[argnum++] = temp++;
5794 while ( (*temp != ' ') && (*temp != '\t') && (*temp != '\0') ) temp++;
5795 if (*temp != '\0') goto splitloop;
5796 }
5797 if (argnum <= 0) return; /* nothing given at all */
5798 cmdstr = _arg[0]; arg = &(_arg[1]); argnum--;
5799
5800 /* lookup command */
5801 cccc = ccc_lookup(cmdstr);
5802 if (cccc == ccccUnknown) /* try some last resorts (missing space on ?/!) */
5803 { if (*cmdstr == '?') cccc = ccccHelp;
5804 #if OPTION_EXECEXT & EXECEXT_SHELL
5805 else if (*cmdstr == '!') cccc = ccccShell;
5806 #endif
5807 else
5808 { err_unknown_cmd:
5809 cc_output_errstr(_("Unknown command. Try \"help\".\n")); return;
5810 }
5811 (_arg[0])++; arg = &(_arg[0]); argnum++;
5812 }
5813 else if (cccc == ccccAmbiguous)
5814 { tMbsIndex idx = ccc_minidx;
5815 tBoolean is_first = truE;
5816 cc_output_errstr(_("Ambiguous command - "));
5817 while (idx <= ccc_maxidx)
5818 { if (is_first) is_first = falsE;
5819 else cc_output_errstr(strCommaSpace);
5820 cc_output_errstr(cccxlat[idx].cmdstr); idx++;
5821 }
5822 cc_output_errstr("?\n"); return;
5823 }
5824
5825 /* handle command */
5826 data = &(cccdata[cccc]); cccf = data->flags;
5827 if ( (cccf & cccfConsoleOnly) && (!cc_on_console) )
5828 { cc_output_errstr(_("Command only available in console mode\n")); return; }
5829 else if ( (!(cccf & cccfDontReqConn)) &&
5830 (!((context->ops->may_handle)(context))) )
5831 { cc_output_errstr(_("Not connected (or no task selected)\n")); return; }
5832 else if (argnum < data->minarg)
5833 { err_tfa: cc_output_errstr(_(strTooFewArguments)); return; }
5834 else if (argnum > data->maxarg) goto err_tma;
5835 else if (cccf & cccfDoQuote) /* a simple case */
5836 { cc_start_cmd(context, data->default_cmdstr, arg, argnum); return; }
5837 switch (cccc)
5838 { case ccccQuit: do_quit(); /*@notreached@*/ break;
5839 case ccccVersion:
5840 /* just in case someone needs the version number after the initial output
5841 has scrolled away, esp. in console mode... */
5842 cc_output_str(strCopyright); cc_output_str(strProgramLegalese); break;
5843 case ccccHelp:
5844 if (argnum <= 0) /* list all possible commands */
5845 { tMbsIndex idx;
5846 const tBoolean on_console = cond2boolean(cc_on_console);
5847 int maxlinewidth = STRBUF_SIZE / 2, linewidth = 80, maxnum, count,
5848 cmdwidth = MAXCMDSTRLEN + 1; /* 1 for space character */
5849 /* IMPLEMENTME: use the actual window/terminal width for linewidth! */
5850 if (linewidth > maxlinewidth) linewidth = maxlinewidth; /* sani... */
5851 else if (linewidth < 25) linewidth = 25; /* ...tize */
5852 if (!on_console) cmdwidth += 2; /* for brackets */
5853 maxnum = linewidth / (cmdwidth + 1); /* "+1" for separation char */
5854 count = 0; temp = strbuf;
5855 for (idx = 0; idx < (tMbsIndex) (ARRAY_ELEMNUM(cccxlat)); idx++)
5856 { const tCccXlat* xlat = &(cccxlat[idx]);
5857 const char *src = xlat->cmdstr, *origtemp;
5858 const tCccCode c = xlat->code;
5859 const tBoolean cannot_use = cond2boolean( (!on_console) &&
5860 (cccdata[c].flags & cccfConsoleOnly) );
5861 char ch;
5862 if (count > 0) *temp++ = ' ';
5863 origtemp = temp;
5864 if (cannot_use) *temp++ = '[';
5865 while ( (ch = *src++) != '\0' ) *temp++ = ch;
5866 if (cannot_use) *temp++ = ']';
5867 count++;
5868 if (count >= maxnum)
5869 { *temp++ = '\n'; *temp = '\0'; cc_output_str(strbuf);
5870 count = 0; temp = strbuf;
5871 }
5872 else { while (temp - origtemp < cmdwidth) *temp++ = ' '; /* fill */ }
5873 }
5874 if (count > 0) { *temp++ = '\n'; *temp = '\0'; cc_output_str(strbuf); }
5875 }
5876 else /* list specific help texts */
5877 { for (i = 0; i < argnum; i++)
5878 { const char* str = arg[i];
5879 const tCccCode c = ccc_lookup(str);
5880 cc_output_str(str);
5881 if (cccc_is_valid(c))
5882 { const tCccData* d = &(cccdata[c]);
5883 const char* defaultstr = d->default_cmdstr;
5884 tCccArgnum minarg = d->minarg, maxarg = d->maxarg;
5885 if (strcmp(str, defaultstr))
5886 { /* mention the non-alias, non-abbreviated command */
5887 cc_output_str(strSpaceOpeningBrace);
5888 if (ccc_abbridx >= 0)
5889 { const char* s = cccxlat[ccc_abbridx].cmdstr;
5890 if (strcmp(s, defaultstr))
5891 { sprint_safe(strbuf, "%s, ", s); cc_output_str(strbuf); }
5892 }
5893 cc_output_str(defaultstr);
5894 cc_output_str(strClosingBrace);
5895 }
5896 cc_output_str(strSpacedDash); cc_output_str(_(d->helpstr));
5897 if (maxarg >= MAXNUM) maxarg = MAXNUM - 1;
5898 if (minarg != maxarg) sprint_safe(numbuf, "..%d", maxarg);
5899 else *numbuf = '\0';
5900 sprint_safe(strbuf, " (%d%s)", minarg, numbuf);
5901 cc_output_str(strbuf);
5902 }
5903 else if (c == ccccAmbiguous)
5904 { tMbsIndex j;
5905 cc_output_str(strSpacedDash);
5906 for (j = ccc_minidx; j <= ccc_maxidx; j++)
5907 { if (j > ccc_minidx) cc_output_str(strCommaSpace);
5908 cc_output_str(cccxlat[j].cmdstr);
5909 }
5910 cc_output_str(strQm);
5911 }
5912 else
5913 { cc_output_str(strSpacedDash); cc_output_errstr(_(strUnknown)); }
5914 cc_output_str(strNewline);
5915 }
5916 }
5917 break;
5918 case ccccHash:
5919 if (argnum <= 0) /* toggle */
5920 { const char* val;
5921 flip_boolean(cc_do_hash);
5922 show_hashing:
5923 if (!cc_do_hash) val = _(strOff);
5924 else { sprint_safe(numbuf, strPercd, cc_hash_bytes); val = numbuf; }
5925 sprint_safe(strbuf, _("Hashmarks: %s\n"), val);
5926 cc_output_str(strbuf);
5927 }
5928 else
5929 { int bytes;
5930 const char* a = arg[0];
5931 if ( (a[0] == '?') && (a[1] == '\0') ) goto show_hashing;
5932 my_atoi(a, &bytes, NULL, MY_ATOI_INT_MAX);
5933 if (bytes < 256) cc_do_hash = falsE;
5934 else { cc_hash_bytes = bytes; cc_do_hash = truE; }
5935 goto show_hashing;
5936 }
5937 break;
5938 #if OPTION_TLS
5939 case ccccAuth:
5940 if (argnum <= 0) ftm = ftmAutodetect;
5941 else
5942 { const char *str = arg[0], ch = *str++;
5943 if ( (!my_islower(ch)) || (*str != '\0') )
5944 { bad_auth: cc_output_errstr(_(strBadValue));
5945 cc_output_errstr(strNewline); break;
5946 }
5947 switch (ch)
5948 { case 'p': ftm = ftmAuthTls; break;
5949 case 'c': ftm = ftmAuthTlsDataclear; break;
5950 case 's': ftm = ftmAuthSsl; break;
5951 default: goto bad_auth; /*@notreached@*/ break;
5952 }
5953 }
5954 cc_start_auth(context, ftm); break;
5955 #endif
5956 case ccccQuote:
5957 sendcmd = NULL; send_cmd: cc_start_cmd(context, sendcmd, arg, argnum);
5958 break;
5959 case ccccUser:
5960 if (argnum <= 0) { arg[0] = strAnonymous; argnum = 1; }
5961 sendcmd = strUser; goto send_cmd; /*@notreached@*/ break;
5962 case ccccPass:
5963 if (argnum <= 0) goto poke_context; /* must prompt user */
5964 else { sendcmd = strPass; goto send_cmd; }
5965 break;
5966 case ccccAccount: sendcmd = "ACCT"; goto send_cmd; /*@notreached@*/ break;
5967 case ccccCd: sendcmd = "CWD"; goto send_cmd; /*@notreached@*/ break;
5968 case ccccLs:
5969 cc_concat(strList, arg, argnum, truE);
5970 cc_sequencer_prepare(context, strbuf, cc_seq_cb_ls);
5971 cc_start_rch(context, rchFtpPasv);
5972 break;
5973 case ccccAscii:
5974 type_ch = 'A'; send_type: sprint_safe(strbuf, "TYPE %c\r\n", type_ch);
5975 cc_do_start_cmd(context, strbuf); break;
5976 case ccccBinary: type_ch = 'I'; goto send_type; /*@notreached@*/ break;
5977 case ccccDelete: sendcmd = "DELE"; goto send_cmd; /*@notreached@*/ break;
5978 case ccccMkdir: sendcmd = "MKD"; goto send_cmd; /*@notreached@*/ break;
5979 case ccccRmdir: sendcmd = "RMD"; goto send_cmd; /*@notreached@*/ break;
5980 case ccccRestart: sendcmd = "REST"; goto send_cmd; /*@notreached@*/ break;
5981 case ccccGet:
5982 if (argnum >= 2) filename = arg[1];
5983 else
5984 { const char* ptr = arg[0];
5985 filename = my_strrchr(ptr, '/');
5986 if (filename == NULL) filename = ptr;
5987 else { filename++; if (*filename == '\0') goto err_tfa; }
5988 }
5989 /* The remainder can only be done by a context-specific handler; e.g. the
5990 "full" local path might depend on a prior ccccLcd result. */
5991 (context->ops->getput)(context, 0, arg[0], filename);
5992 break;
5993 case ccccOpen:
5994 if (context->ops->try_open(context)) /* can open a new connection */
5995 { tUriData* uri = uri_parse(arg[0], NULL, NULL, NULL, 1);
5996 const tResourceProtocol rp = uri->rp;
5997 tResourceError re = uri->re;
5998 tResourceRequest* request;
5999 char* spfbuf;
6000 my_spf(strbuf, STRBUF_SIZE, &spfbuf, "%s%s\n", _(strUriColonSpace),
6001 uri->uri);
6002 cc_output_str(spfbuf); my_spf_cleanup(strbuf, spfbuf);
6003 if ( (re == reFine) && (!is_ftplike(rp)) ) re = reProtocol;
6004 if (re != reFine)
6005 { cc_output_errstr(strResourceError[re]);
6006 cc_output_errstr(strNewline); uri_put(uri); return;
6007 }
6008 request = request_create(); uri_attach(request->uri_data, uri);
6009 (context->ops->do_open)(context, request);
6010 }
6011 break;
6012 default: /* try the context-specific handler */
6013 poke_context:
6014 if (!((context->ops->handle_cccc)(context, cccc, arg, argnum)))
6015 goto err_unknown_cmd;
6016 break;
6017 }
6018 }
6019
6020 #endif /* #if CONFIG_CUSTOM_CONN */
6021
6022
6023 /** Console mode */
6024
6025 #if CONFIG_CONSOLE
6026
6027 static const char strTooManyTasks[] = N_("Too many tasks\n");
6028
6029 typedef /*signed*/ int tConsoleTaskNum; /* ("signed" for simplicity only) */
6030 #define CONSOLE_TASK_INVALID (-1)
6031
6032 my_enum1 enum
6033 { ctsUnused = 0, ctsPromptable = 1, ctsBusy = 2, ctsAutomated = 3
6034 } my_enum2(unsigned char) tConsoleTaskState;
6035
6036 typedef struct
6037 { tCustomConnContext context;
6038 tCcSeqEntry sequencer_entry;
6039 tResourceRequest* request;
6040 const char* label;
6041 size_t last_hash_bytecount;
6042 tConsoleTaskState state;
6043 } tConsoleTask;
6044
6045 static tConsoleTask* console_task = NULL;
6046 static tConsoleTaskNum console_task_num = 0,
6047 console_task_curr = CONSOLE_TASK_INVALID;
6048 static tBoolean console_try_prompt = falsE;
6049 #define console_prompting ( (console_task_curr == CONSOLE_TASK_INVALID) || \
6050 (console_task[console_task_curr].state == ctsPromptable) )
6051 #define console_ctxidx ( (tConsoleTaskNum) (MY_POINTER_TO_INT(context->data)) )
6052
6053 static const tCustomConnOps console_ops; /* prototype */
6054 static const tCustomConnContext console_dummyctx = { &console_ops,
6055 MY_INT_TO_POINTER(CONSOLE_TASK_INVALID) };
6056 #define console_currctx ( (console_task_curr == CONSOLE_TASK_INVALID) ? &console_dummyctx : &(console_task[console_task_curr].context) )
6057
6058 my_enum1 enum
6059 { cpkNormal = 0, cpkPassword = 1, cpkOverwrite = 2
6060 } my_enum2(unsigned char) tConsolePromptKind;
6061 #define console_pk_disguising (console_pk == cpkPassword)
6062
6063 static tConsolePromptKind console_pk = cpkNormal;
6064
6065 static char* console_input = NULL;
6066 static size_t console_input_len = 0, console_input_maxlen = 0;
6067
6068 static const char* console_cwd = NULL;
6069 static char console_oac[4]; /* for the "(o)verwrite/(a)ppend/(c)ancel" keys */
6070
6071 #define task_id2idx(id) ((id) - 1)
6072 #define task_idx2id(idx) ((idx) + 1)
6073
console_print_prompt(void)6074 static void console_print_prompt(void)
6075 { const char *extra, *spacing;
6076 if (!console_prompting) return;
6077 if (console_task_curr == CONSOLE_TASK_INVALID) *strbuf2 = '\0';
6078 else
6079 { const char* using_tls;
6080 #if OPTION_TLS
6081 const tResourceRequest* request = console_task[console_task_curr].request;
6082 const tResource* resource;
6083 if ( (request != NULL) && ( (resource = request->resource) != NULL ) &&
6084 (resource_in_tls(resource, falsE)) )
6085 using_tls = "|TLS";
6086 else
6087 #endif
6088 { using_tls = strEmpty; }
6089 sprint_safe(strbuf2, "[%d%s]", task_idx2id(console_task_curr), using_tls);
6090 }
6091 switch (console_pk)
6092 { default: extra = strEmpty; break;
6093 case cpkPassword: extra = strPass; break;
6094 case cpkOverwrite: extra = console_oac; break;
6095 }
6096 spacing = ( (*extra == '\0') ? strEmpty : strSpace );
6097 sprint_safe(strbuf, "\n%s%s%s%s> ", strbuf2, spacing, extra, spacing);
6098 cc_output_str(strbuf); console_try_prompt = falsE;
6099 }
6100
console_task_print_info(tConsoleTaskNum idx,const char * prefix)6101 static void console_task_print_info(tConsoleTaskNum idx, const char* prefix)
6102 { const tConsoleTask* task;
6103 tConsoleTaskState state;
6104 const tResourceRequest* request;
6105 const char *message, *label, *statestr, *using_tls;
6106 char* spfbuf;
6107 tBoolean has_label, is_error;
6108 if (idx == CONSOLE_TASK_INVALID) return; /* CHECKME: print error message? */
6109 task = &(console_task[idx]); request = task->request; state = task->state;
6110 label = task->label; has_label = cond2boolean(label != NULL);
6111 switch (state)
6112 { default: statestr = strEmpty; break;
6113 case ctsBusy: statestr = _("busy"); break;
6114 case ctsAutomated: statestr = _("automated"); break;
6115 }
6116 #if OPTION_TLS
6117 if ((request->resource!=NULL) && (resource_in_tls(request->resource,falsE)))
6118 using_tls = " - TLS";
6119 else
6120 #endif
6121 { using_tls = strEmpty; }
6122 my_spf(strbuf, STRBUF_SIZE, &spfbuf, "%s%s %d%s%s%s%s%s%s - \"%s\"\n",
6123 null2empty(prefix), _("Task"), task_idx2id(idx), (has_label ? " (\"" :
6124 strEmpty), null2empty(label), (has_label ? "\")" : strEmpty), ( (*statestr
6125 != '\0') ? strSpacedDash : strEmpty ), statestr, using_tls,
6126 request->uri_data->uri); /*so much to print, so little time^Wspace... :-)*/
6127 cc_output_str(spfbuf); my_spf_cleanup(strbuf, spfbuf);
6128 message = calculate_reqresmsg(request, request->resource, 1, &is_error);
6129 if (*message != '\0') { cc_output_str(message); cc_output_str(strNewline); }
6130 }
6131
console_task_switch(tConsoleTaskNum idx,tBoolean did_create)6132 static void console_task_switch(tConsoleTaskNum idx, tBoolean did_create)
6133 /* Task switch? Who's writing an operating system here?! :-) */
6134 { tConsoleTask* task = &(console_task[idx]);
6135 const tResourceRequest* request = task->request;
6136 const tResource* resource = request->resource;
6137 console_task_curr = idx;
6138 console_task_print_info(idx, (did_create ? _("Creating: ") :
6139 _("Switching to: ")));
6140 if (resource != NULL)
6141 { task->last_hash_bytecount = resource->bytecount;
6142 /* (to ensure that the user isn't "flooded" with hashmarks when making a
6143 task current which transferred lots of data while non-current) */
6144 }
6145 }
6146
6147 #define CC_MAXTASKNUM (4096) /* just a "sane" limit (rather insane in fact) */
6148
console_task_create(void)6149 static tConsoleTaskNum console_task_create(void)
6150 { tConsoleTaskNum idx = 0, tempidx;
6151 /* try to find a free slot */
6152 while (idx < console_task_num)
6153 { if (console_task[idx].state == ctsUnused)
6154 { tConsoleTask* task;
6155 tCustomConnContext* context;
6156 pick_it: task = &(console_task[idx]); task->state = ctsBusy;
6157 context = &(task->context); context->ops = &console_ops;
6158 context->data = MY_INT_TO_POINTER(idx); goto out;
6159 }
6160 idx++;
6161 }
6162 /* no free slot left, allocate more memory; we start with very few (two)
6163 slots because most users will even only have _one_ task existing at any
6164 time; don't waste memory... */
6165 idx = tempidx = console_task_num;
6166 if (console_task_num < 2) console_task_num = 2;
6167 else if (console_task_num < CC_MAXTASKNUM) console_task_num <<= 1;
6168 else goto fail;
6169 console_task = memory_reallocate(console_task, console_task_num *
6170 sizeof(tConsoleTask), mapOther);
6171 while (tempidx < console_task_num)
6172 { my_memclr_var(console_task[tempidx]); tempidx++; }
6173 goto pick_it;
6174 fail: idx = CONSOLE_TASK_INVALID;
6175 out: return(idx);
6176 }
6177
console_task_remove(tConsoleTaskNum idx,tBoolean print_notice)6178 static void console_task_remove(tConsoleTaskNum idx, tBoolean print_notice)
6179 { tConsoleTask* task = &(console_task[idx]);
6180 tResourceRequest* request = task->request;
6181 tResource* resource = ( (request != NULL) ? request->resource : NULL );
6182 if (print_notice) console_task_print_info(idx, _("Removing: "));
6183 cc_sequencer_cleanup(&(task->context), &(task->sequencer_entry), resource);
6184 request_remove(request); __dealloc(task->label);
6185 my_memclr_var(console_task[idx]); /* esp. setting state to ctsUnused */
6186 if (console_task_curr == idx) /* try to find a new "current" task */
6187 { console_task_curr = CONSOLE_TASK_INVALID; /* IMPLEMENTME: nicer task? */
6188 console_try_prompt = truE;
6189 }
6190 }
6191
task_str2idx(const char * str)6192 static tConsoleTaskNum task_str2idx(const char* str)
6193 { tConsoleTaskNum id, idx;
6194 const char* tail;
6195 my_atoi(str, &id, &tail, MY_ATOI_INT_MAX);
6196 if ( (id <= 0) || (id > console_task_num) || (*tail != '\0') )
6197 { failed: idx = CONSOLE_TASK_INVALID; goto out; }
6198 idx = task_id2idx(id);
6199 if (console_task[idx].state == ctsUnused) goto failed;
6200 out:
6201 return(idx);
6202 }
6203
say_task_unknown(const char * str)6204 static void say_task_unknown(const char* str)
6205 { char* spfbuf;
6206 my_spf(strbuf, STRBUF_SIZE, &spfbuf, "%s - %s\n", str, _(strUnknown));
6207 cc_output_errstr(spfbuf); my_spf_cleanup(strbuf, spfbuf);
6208 }
6209
console_may_handle(__sunused const tCustomConnContext * context __cunused)6210 static tBoolean console_may_handle(__sunused const tCustomConnContext* context
6211 __cunused)
6212 { return(cond2boolean(console_task_curr != CONSOLE_TASK_INVALID));
6213 }
6214
console_print_hashmarks(tConsoleTask * task,const tResource * resource)6215 static void console_print_hashmarks(tConsoleTask* task,
6216 const tResource* resource)
6217 { size_t old, curr, diff, num, count, c;
6218 old = task->last_hash_bytecount; curr = resource->bytecount;
6219 if (curr <= old) return; /* "should not happen" */
6220 diff = curr - old; num = diff / cc_hash_bytes;
6221 if (num > 0)
6222 { task->last_hash_bytecount += num * cc_hash_bytes;
6223 while (num > 0)
6224 { char* buf = strbuf;
6225 if (num < STRBUF_SIZE / 2) count = num;
6226 else count = STRBUF_SIZE / 2;
6227 num -= count;
6228 for (c = 0; c < count; c++) *buf++ = '#';
6229 *buf = '\0'; cc_output_str(strbuf);
6230 }
6231 }
6232 }
6233
rwd_cb_ctr(const tRemainingWork * rw)6234 static tBoolean rwd_cb_ctr(const tRemainingWork* rw)
6235 { tConsoleTaskNum task_idx = (tConsoleTaskNum) MY_POINTER_TO_INT(rw->data1);
6236 console_task_remove(task_idx, cond2boolean(task_idx == console_task_curr));
6237 if (console_try_prompt) console_print_prompt();
6238 return(falsE);
6239 }
6240
console_schedule_task_removal(tConsoleTaskNum task_idx)6241 static void console_schedule_task_removal(tConsoleTaskNum task_idx)
6242 /* cf. schedule_request_removal() */
6243 { tRemainingWork* rw = remaining_work_create(rwd_cb_ctr);
6244 rw->data1 = MY_INT_TO_POINTER(task_idx);
6245 }
6246
6247 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
6248
console_download_request_callback(void * _idx,tDhmNotificationFlags flags)6249 static void console_download_request_callback(void* _idx,
6250 tDhmNotificationFlags flags)
6251 { const tConsoleTaskNum task_idx = (tConsoleTaskNum) MY_POINTER_TO_INT(_idx);
6252 tConsoleTask* task = &(console_task[task_idx]);
6253 const tResourceRequest* request = task->request;
6254 tResource* resource = request->resource;
6255 #if CONFIG_DEBUG
6256 sprint_safe(debugstrbuf, "console_drc(): idx=%d, dhmnf=%d, req=%p, res=%p\n",
6257 task_idx, flags, request, resource);
6258 debugmsg(debugstrbuf);
6259 #endif
6260 if (flags & (dhmnfDataChange | dhmnfMetadataChange))
6261 { const tBoolean is_curr = cond2boolean(task_idx == console_task_curr);
6262 tResourceError re;
6263 if (resource != NULL)
6264 { re = resource->error;
6265 if (re != reFine)
6266 { if (is_curr)
6267 { const char* text;
6268 #if OPTION_TLS
6269 if ( (re == reTls) && (tls_errtext(resource, strbuf)) ) text=strbuf;
6270 else
6271 #endif
6272 { handle_re: text = _(strResourceError[re]); }
6273 cc_output_errstr(text); cc_output_errstr(strNewline);
6274 }
6275 remove_task: console_schedule_task_removal(task_idx); return;
6276 }
6277 else if (resource->flags & rfFinal)
6278 { if (is_curr) { cc_output_str(_(strDone)); cc_output_str(strNewline); }
6279 goto remove_task;
6280 }
6281 else if ( (cc_do_hash) && (is_curr) )
6282 console_print_hashmarks(task, resource);
6283 }
6284 else if ( (re = request->error) != reFine )
6285 { if (is_curr) goto handle_re;
6286 else goto remove_task;
6287 }
6288 }
6289 else if (flags & dhmnfAttachery) /* a resource was attached to the request */
6290 { dhm_notification_setup(resource, console_download_request_callback,
6291 _idx, dhmnfDataChange | dhmnfMetadataChange, dhmnSet);
6292 }
6293 }
6294
console_download(const char * from,const char * to)6295 static one_caller void console_download(const char* from, const char* to)
6296 { unsigned char _cleanup = 0; /* "&1": <to>; "&2": <fd>; "&4": <prr_data> */
6297 tUriData* uri_data = uri_parse(from, NULL, NULL, NULL, 0);
6298 tResourceError re = uri_data->re;
6299 int fd SHUT_UP_COMPILER(-1), err, e;
6300 struct stat statbuf;
6301 mode_t mode;
6302 char* spfbuf;
6303 tPrrData prr_data;
6304 tResourceRequest* request;
6305 tConsoleTaskNum task_idx;
6306 tConsoleTask* task;
6307
6308 if (re != reFine)
6309 { cc_output_errstr(_(strResourceError[re])); cc_output_errstr(strNewline);
6310 goto cleanup;
6311 }
6312 if (to == NULL)
6313 { to = uri2filename(uri_data);
6314 if (to == NULL)
6315 { cc_output_errstr(_(strTooFewArguments)); goto cleanup; }
6316 _cleanup |= 1;
6317 }
6318
6319 /* CHECKME: apply finalize_path()? */
6320 if (*to == '/') { /* absolute path, don't change it */ }
6321 else if ( (to[0] == '~') && (to[1] == '/') )
6322 { my_spf(NULL, 0, &spfbuf, strPercsPercs, get_homepath(), to + 2);
6323 if (_cleanup & 1) memory_deallocate(to);
6324 to = my_spf_use(spfbuf); _cleanup |= 1;
6325 }
6326 else
6327 { my_spf(NULL, 0, &spfbuf, strPercsPercs, ( (console_cwd != NULL) ?
6328 console_cwd : my_getcwd() ), to);
6329 if (_cleanup & 1) memory_deallocate(to);
6330 to = my_spf_use(spfbuf); _cleanup |= 1;
6331 }
6332 my_spf(strbuf, STRBUF_SIZE, &spfbuf, "%s \"%s\" -> \"%s\"\n",
6333 _(strUcDownload), uri_data->uri, to);
6334 cc_output_str(spfbuf); my_spf_cleanup(strbuf, spfbuf);
6335
6336 /* IMPLEMENTME: don't use O_EXCL unconditionally, do my_stat() first, ask
6337 "may overwrite?" if file already exists! */
6338 fd = my_create(to, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
6339 if (fd < 0)
6340 { e = ( (fd == -1) ? errno : 0 );
6341 failed_fd: inform_about_error(e, _(strCantDownload));
6342 goto cleanup;
6343 }
6344 _cleanup |= 2;
6345 if ( (err = my_fstat(fd, &statbuf)) != 0 )
6346 { e = ( (err == -1) ? errno : 0 ); goto failed_fd; }
6347 mode = statbuf.st_mode;
6348 if (!S_ISREG(mode))
6349 { e = ( (S_ISDIR(mode)) ? EISDIR : 0 ); goto failed_fd; }
6350
6351 prr_setup(&prr_data, from, prrfNone); prepare_resource_request(&prr_data);
6352 _cleanup |= 4; request = prr_data.result; re = request->error;
6353 if (re != reFine)
6354 { inform_about_error(0, _(strResourceError[re])); goto cleanup; }
6355
6356 task_idx = console_task_create();
6357 if (task_idx == CONSOLE_TASK_INVALID) /* "rare" */
6358 { cc_output_errstr(_(strTooManyTasks)); goto cleanup; }
6359 task = &(console_task[task_idx]); task->state = ctsAutomated;
6360 task->request = request; prr_data.result = NULL;
6361 console_task_switch(task_idx, truE);
6362 _cleanup &= ~2; /* we'll _use_ the fd, so don't close it */
6363 dhm_notification_setup(request, console_download_request_callback,
6364 MY_INT_TO_POINTER(task_idx), dhmnfDataChange | dhmnfMetadataChange |
6365 dhmnfAttachery, dhmnSet);
6366 download_queue(request, fd);
6367 cleanup:
6368 uri_put(uri_data);
6369 if (_cleanup & 1) memory_deallocate(to);
6370 if (_cleanup & 2) my_close(fd);
6371 if (_cleanup & 4) prr_setdown(&prr_data);
6372 }
6373
6374 #endif /* #if CONFIG_EXTRA & EXTRA_DOWNLOAD */
6375
6376 #if OPTION_EXECEXT & EXECEXT_SHELL
6377
console_execext_shell_request_callback(void * _idx,tDhmNotificationFlags flags)6378 static void console_execext_shell_request_callback(void* _idx,
6379 tDhmNotificationFlags flags)
6380 { const tConsoleTaskNum task_idx = (tConsoleTaskNum) MY_POINTER_TO_INT(_idx);
6381 tConsoleTask* task = &(console_task[task_idx]);
6382 const tResourceRequest* request = task->request;
6383 tResource* resource = request->resource;
6384 tResourceError re;
6385 #if CONFIG_DEBUG
6386 sprint_safe(debugstrbuf,
6387 "console_esrc(): task_idx=%d, flags=%d, req=%p, res=%p\n",
6388 task_idx, flags, request, resource);
6389 debugmsg(debugstrbuf);
6390 #endif
6391 if (flags & (dhmnfDataChange | dhmnfMetadataChange))
6392 { const tBoolean is_curr = cond2boolean(task_idx == console_task_curr);
6393 if (resource != NULL)
6394 { if ( (re = resource->error) != reFine )
6395 { handle_re: if (is_curr) inform_about_error(0, _(strResourceError[re]));
6396 do_remove: console_schedule_task_removal(task_idx);
6397 }
6398 else if (resource->flags & rfFinal)
6399 { if (is_curr) { cc_output_str(_(strDone)); cc_output_str(strNewline); }
6400 goto do_remove;
6401 }
6402 }
6403 else if ( (re = request->error) != reFine ) goto handle_re;
6404 }
6405 else if (flags & dhmnfAttachery) /* a resource was attached to the request */
6406 { resource->custconn_handle = _idx;
6407 dhm_notification_setup(resource, console_execext_shell_request_callback,
6408 _idx, dhmnfDataChange | dhmnfMetadataChange, dhmnSet);
6409 }
6410 }
6411
console_execext_shell(const char * cmd)6412 static one_caller void console_execext_shell(const char* cmd)
6413 /* This is roughly a simplified variant of handle_lig_execext_shell(). */
6414 { tExecextShellData data;
6415 tExecextShellFlags esf = esfNone;
6416 tBoolean want_header = falsE;
6417 if (*cmd == ':') /* options given */
6418 { char ch;
6419 while ( (ch = *++cmd) != '\0' )
6420 { switch (ch)
6421 { case 'o': esf |= esfReadStdout; break;
6422 case 'e': esf |= esfReadStderr; break;
6423 case 'h': want_header = truE; break;
6424 case ' ': cmd++; goto end_of_options; /*@notreached@*/ break;
6425 }
6426 }
6427 end_of_options: {}
6428 }
6429 if (*cmd == '\0') { inform_about_error(0, _(strNoShellCmd)); return; }
6430 my_memclr_var(data); data.esf = esf; data.command = cmd;
6431 if (esf & (esfReadStdout | esfReadStderr))
6432 { tPrrData prr_data;
6433 tResourceRequest* request;
6434 tResourceError re;
6435 tConsoleTaskNum task_idx;
6436 tConsoleTask* task;
6437 prr_setup(&prr_data, strExecextShellColon /* dummy - CHECKME! */,
6438 prrfUpsfp4);
6439 prepare_resource_request(&prr_data);
6440 if ( (re = prr_data.result->error) != reFine )
6441 { inform_about_error(0, _(strResourceError[re]));
6442 cleanup_prr: prr_setdown(&prr_data); return;
6443 }
6444 task_idx = console_task_create();
6445 if (task_idx == CONSOLE_TASK_INVALID) /* "rare" */
6446 { cc_output_errstr(_(strTooManyTasks)); goto cleanup_prr; }
6447 task = &(console_task[task_idx]); task->state = ctsAutomated;
6448 task->request = data.request = request = prr_data.result;
6449 prr_data.result = NULL; prr_setdown(&prr_data);
6450 dhm_notification_setup(request, console_execext_shell_request_callback,
6451 MY_INT_TO_POINTER(task_idx), dhmnfDataChange | dhmnfMetadataChange |
6452 dhmnfAttachery, dhmnSet);
6453 console_task_switch(task_idx, truE);
6454 }
6455 if (want_header) data.writedata = execext_shell_header();
6456 resource_start_execext_shell(&data);
6457 if (want_header) my_spf_cleanup(strbuf2, data.writedata);
6458 }
6459
6460 #endif /* #if OPTION_EXECEXT & EXECEXT_SHELL */
6461
console_handle_cccc(__sunused const tCustomConnContext * context __cunused,tCccCode cccc,const char ** arg,tCccArgnum argnum)6462 static tBoolean console_handle_cccc(__sunused const tCustomConnContext*
6463 context __cunused, tCccCode cccc, const char** arg, tCccArgnum argnum)
6464 /* console-specific custom connection command handler; IMPLEMENTME further! */
6465 { static const char strNoTask[] = N_("no task\n");
6466 tBoolean retval = truE;
6467 tConsoleTaskNum task_idx;
6468 tConsoleTask* task;
6469 const char* str;
6470 char *spfbuf, *new_cwd;
6471 switch (cccc)
6472 { case ccccLcd: /* change/print local current directory */
6473 /* We don't "actually" change the directory, e.g. by calling chdir(). We
6474 only change an internal prefix string; that causes less trouble. */
6475 if (argnum <= 0) new_cwd = my_strdup(my_getcwd());
6476 else
6477 { const char* dir = arg[0];
6478 const size_t dirlen = strlen(dir);
6479 const tBoolean has_slash = cond2boolean( (dirlen > 0) &&
6480 (dir[dirlen - 1] == '/') );
6481 if (*dir == '/')
6482 { if (has_slash) new_cwd = my_strdup(dir);
6483 else
6484 { new_cwd = __memory_allocate(dirlen + 1 + 1, mapString);
6485 my_memcpy(new_cwd, dir, dirlen);
6486 new_cwd[dirlen] = '/'; new_cwd[dirlen + 1] = '\0';
6487 }
6488 }
6489 else if ( (dir[0] == '~') && (dir[1] == '/') )
6490 { my_spf(strbuf, STRBUF_SIZE, &spfbuf, strPercsPercs, get_homepath(),
6491 dir + 2);
6492 new_cwd = finalize_path(spfbuf); my_spf_cleanup(strbuf, spfbuf);
6493 }
6494 else
6495 { my_spf(strbuf, STRBUF_SIZE, &spfbuf, "%s%s%s", ((console_cwd != NULL)
6496 ? console_cwd : my_getcwd()), dir, has_slash ? strEmpty :strSlash);
6497 new_cwd = finalize_path(spfbuf); my_spf_cleanup(strbuf, spfbuf);
6498 }
6499 }
6500 /* IMPLEMENTME: test whether this directory exists at all? */
6501 __dealloc(console_cwd); console_cwd = new_cwd;
6502 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
6503 _("Local current directory: \"%s\"\n"), console_cwd);
6504 cc_output_str(spfbuf); my_spf_cleanup(strbuf, spfbuf);
6505 break;
6506 case ccccTask: /* switch to a task */
6507 str = arg[0]; task_idx = task_str2idx(str);
6508 if (task_idx == CONSOLE_TASK_INVALID)
6509 { if ( (str[0] == '0') && (str[1] == '\0') )
6510 console_task_curr = CONSOLE_TASK_INVALID;
6511 else { stu: say_task_unknown(str); }
6512 }
6513 else console_task_switch(task_idx, falsE);
6514 break;
6515 case ccccInfo: /* print information about existing tasks */
6516 if (argnum <= 0) /* show all tasks */
6517 { tBoolean is_first = truE;
6518 for (task_idx = 0; task_idx < console_task_num; task_idx++)
6519 { if (console_task[task_idx].state != ctsUnused)
6520 { console_task_print_info(task_idx, NULL); is_first = falsE; }
6521 }
6522 if (is_first) { say_no_task: cc_output_str(_(strNoTask)); }
6523 }
6524 else /* show given tasks */
6525 { tCccArgnum i;
6526 for (i = 0; i < argnum; i++)
6527 { str = arg[i]; task_idx = task_str2idx(str);
6528 if (task_idx == CONSOLE_TASK_INVALID) goto stu;
6529 else console_task_print_info(task_idx, NULL);
6530 }
6531 }
6532 break;
6533 case ccccLabel:
6534 if (my_isdigit(*(arg[0])))
6535 { cc_output_errstr(_(strBadValue)); cc_output_errstr(strNewline); break;}
6536 if (argnum <= 1)
6537 { task_idx = console_task_curr;
6538 if (task_idx == CONSOLE_TASK_INVALID) goto say_no_task;
6539 }
6540 else
6541 { str = arg[1]; task_idx = task_str2idx(str);
6542 if (task_idx == CONSOLE_TASK_INVALID) goto stu;
6543 }
6544 task = &(console_task[task_idx]); my_strdedup(task->label, arg[0]);
6545 break;
6546 case ccccPass: /* the user wants to be prompted separately */
6547 console_pk = cpkPassword; break;
6548 case ccccClose:
6549 if (argnum <= 0)
6550 { if (console_task_curr == CONSOLE_TASK_INVALID)
6551 cc_output_errstr(_(strNoTask));
6552 else console_task_remove(console_task_curr, truE);
6553 }
6554 else
6555 { tCccArgnum i;
6556 for (i = 0; i < argnum; i++)
6557 { str = arg[i]; task_idx = task_str2idx(str);
6558 if (task_idx == CONSOLE_TASK_INVALID) goto stu; /*CHECKME: proceed?*/
6559 else console_task_remove(task_idx, truE);
6560 }
6561 }
6562 break;
6563 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
6564 case ccccDownload:
6565 console_download(arg[0], ( (argnum >= 2) ? arg[1] : NULL )); break;
6566 #endif
6567 #if OPTION_EXECEXT & EXECEXT_SHELL
6568 case ccccShell:
6569 { const char* cmd;
6570 cc_concat(NULL, arg, argnum, falsE);
6571 cmd = my_strdup(strbuf); /* IMPROVEME! */
6572 console_execext_shell(cmd); memory_deallocate(cmd);
6573 }
6574 break;
6575 #endif
6576 default: retval = falsE; break;
6577 }
6578 return(retval);
6579 }
6580
console_try_open(__sunused const tCustomConnContext * context __cunused)6581 static tBoolean console_try_open(__sunused const tCustomConnContext* context
6582 __cunused)
6583 { const tBoolean retval = cond2boolean(console_task_num < CC_MAXTASKNUM);
6584 if (!retval) cc_output_errstr(_(strTooManyTasks));
6585 return(retval);
6586 }
6587
console_request_callback(void * _idx,tDhmNotificationFlags flags)6588 static void console_request_callback(void* _idx, tDhmNotificationFlags flags)
6589 { tConsoleTaskNum idx = (tConsoleTaskNum) MY_POINTER_TO_INT(_idx);
6590 tConsoleTask* task = &(console_task[idx]);
6591 tResourceRequest* request = task->request;
6592 tResource* resource = request->resource;
6593 #if CONFIG_DEBUG
6594 sprint_safe(debugstrbuf,
6595 "console_request_callback(_idx=%p, idx=%d, req=%p, res=%p, flags=%d)\n",
6596 _idx, idx, request, resource, flags);
6597 debugmsg(debugstrbuf);
6598 #endif
6599 if (flags & (dhmnfDataChange | dhmnfMetadataChange))
6600 { tResourceError re;
6601 if (resource != NULL)
6602 { if ( (re = resource->error) != reFine )
6603 { const char* text;
6604 #if OPTION_TLS
6605 if ( (re == reTls) && (tls_errtext(resource, strbuf)) ) text = strbuf;
6606 else
6607 #endif
6608 { handle_re: text = _(strResourceError[re]); }
6609 if (idx == console_task_curr) inform_about_error(0, text);
6610 console_schedule_task_removal(idx); goto out;
6611 }
6612 }
6613 else
6614 { if ( (re = request->error) != reFine ) goto handle_re;
6615 }
6616 }
6617 else if (flags & dhmnfAttachery) /* a resource was attached to the request */
6618 { dhm_notification_setup(resource, console_request_callback, _idx,
6619 dhmnfDataChange | dhmnfMetadataChange, dhmnSet);
6620 resource->flags |= rfCustomConn; resource->custconn_handle = _idx;
6621 }
6622 out:
6623 if (console_try_prompt) console_print_prompt();
6624 }
6625
console_do_open(__sunused const tCustomConnContext * context __cunused,tResourceRequest * request)6626 static void console_do_open(__sunused const tCustomConnContext* context
6627 __cunused, tResourceRequest* request)
6628 { const tConsoleTaskNum idx = console_task_create();
6629 if (idx == CONSOLE_TASK_INVALID) /* "can't happen" */
6630 { request_remove(request); return; }
6631 console_task[idx].request = request; console_task_switch(idx, truE);
6632 dhm_notification_setup(request, console_request_callback,
6633 MY_INT_TO_POINTER(idx), dhmnfDataChange | dhmnfMetadataChange |
6634 dhmnfAttachery, dhmnSet);
6635 request_queue(request, rraCustomConn);
6636 }
6637
console_do_start(const tCustomConnContext * context,unsigned char what,const void * whatever)6638 static void console_do_start(const tCustomConnContext* context,
6639 unsigned char what, const void* whatever)
6640 { tConsoleTask* task = &(console_task[console_ctxidx]);
6641 tResource* resource = task->request->resource;
6642 if ( (what == 2) && (resource->uri_data->rp != rpFtp) )
6643 { /* a _manual_ "auth" command can only work on "ftp", not "ftps" */
6644 cc_output_errstr(_(strResourceError[reProtocol]));
6645 cc_output_errstr(strNewline); return;
6646 }
6647 /* IMPLEMENTME: check whether already in TLS mode (if what == 2)! */
6648 task->state = ctsBusy; resource_custom_conn_start(resource, what, whatever);
6649 }
6650
console_do_prepare_sequencer(const tCustomConnContext * context,const char * cmd,tCcSeqCallback callback)6651 static void console_do_prepare_sequencer(const tCustomConnContext* context,
6652 const char* cmd, tCcSeqCallback callback)
6653 { tConsoleTask* task = &(console_task[console_ctxidx]);
6654 tCcSeqEntry* entry = &(task->sequencer_entry);
6655 my_strdedup(entry->data, cmd); entry->callback = callback;
6656 }
6657
6658 static const char *console_from = NULL, *console_to = NULL; /* paths */
6659
console_setup_retr(const tCustomConnContext * context,const char * from,const char * to,tFileTreatment ft)6660 static void console_setup_retr(const tCustomConnContext* context,
6661 const char* from, const char* to, tFileTreatment ft)
6662 { int fd, err, e;
6663 struct stat statbuf;
6664 mode_t mode;
6665 tConsoleTask* task;
6666 tResourceRequest* request;
6667 tResource* resource;
6668 if (ft == ftAppend) fd = my_open(to, O_WRONLY | O_APPEND);
6669 else
6670 { int cflags = O_CREAT | O_TRUNC | O_WRONLY;
6671 if (ft == ftDontOverwrite) cflags |= O_EXCL;
6672 fd = my_create(to, cflags, S_IRUSR | S_IWUSR);
6673 }
6674 #if CONFIG_DEBUG
6675 e = errno;
6676 sprint_safe(debugstrbuf, "setup_retr(): ft=%d, fd=%d, errno=%d\n", ft, fd,
6677 errno);
6678 debugmsg(debugstrbuf); errno = e;
6679 #endif
6680 if (fd < 0)
6681 { e = ( (fd == -1) ? errno : 0 );
6682 handle_re_file: inform_about_error(e, _(strResourceError[reFile]));
6683 return;
6684 }
6685 if ( (err = my_fstat(fd, &statbuf)) != 0 )
6686 { e = ( (err == -1) ? errno : 0 ); clre: my_close(fd); goto handle_re_file; }
6687 mode = statbuf.st_mode;
6688 if (!S_ISREG(mode)) { e = ( (S_ISDIR(mode)) ? EISDIR : 0 ); goto clre; }
6689 task = &(console_task[console_ctxidx]); request = task->request;
6690 resource = request->resource;
6691 sinking_data_deallocate(&(resource->sinking_data));
6692 sinking_data_mcleanup(); sinking_data.download_fd = fd;
6693 sinking_data.flags |= sdfIsDownloadFdValid;
6694 sinking_data_shift(&(resource->sinking_data));
6695 cc_concat(strRetr, &from, 1, truE);
6696 cc_sequencer_prepare(context, strbuf, cc_seq_cb_retr);
6697 cc_start_rch(context, rchFtpPasv);
6698 }
6699
console_handle_oac(const char * str)6700 static one_caller void console_handle_oac(const char* str)
6701 { const char ch = *str++;
6702 tFileTreatment ft;
6703 if ( (ch == '\0') || (*str != '\0') )
6704 { cc_output_errstr(_(strBadValue)); cc_output_errstr(strNewline); return; }
6705 if (ch == console_oac[0]) ft = ftMayOverwrite;
6706 else if (ch == console_oac[1]) ft = ftAppend;
6707 else { cc_output_str(_("Cancelled\n")); return; }
6708 console_setup_retr(console_currctx, console_from, console_to, ft);
6709 }
6710
console_getput(const tCustomConnContext * context,unsigned char what,const char * s1,const char * s2)6711 static void console_getput(const tCustomConnContext* context,
6712 unsigned char what, const char* s1, const char* s2)
6713 { const char *from, *to;
6714 char* spfbuf;
6715 tBoolean must_dealloc_to = falsE;
6716 struct stat statbuf;
6717 int err, e;
6718 if (what == 0) /* get */
6719 { from = s1; to = s2;
6720 /* CHECKME: apply finalize_path()? */
6721 if (*to == '/') { /* absolute path, don't change it */ }
6722 else if ( (to[0] == '~') && (to[1] == '/') )
6723 { my_spf(NULL, 0, &spfbuf, strPercsPercs, get_homepath(), to + 2);
6724 to = my_spf_use(spfbuf); must_dealloc_to = truE;
6725 }
6726 else
6727 { while ( (to[0] == '.') && (to[1] == '/') ) to += 2;
6728 my_spf(NULL, 0, &spfbuf, strPercsPercs, ( (console_cwd != NULL) ?
6729 console_cwd : my_getcwd() ), to);
6730 to = my_spf_use(spfbuf); must_dealloc_to = truE;
6731 }
6732 my_spf(strbuf, STRBUF_SIZE, &spfbuf, "\"%s\" -> \"%s\"\n", from, to);
6733 cc_output_str(spfbuf); my_spf_cleanup(strbuf, spfbuf);
6734 err = my_stat(to, &statbuf);
6735 if (err != 0)
6736 { e = ( (err == -1) ? (errno) : 0 );
6737 if (e == ENOENT) console_setup_retr(context, from, to,ftDontOverwrite);
6738 else
6739 { handle_re_file: inform_about_error(e, _(strResourceError[reFile]));
6740 goto cleanup;
6741 }
6742 }
6743 else
6744 { static tBoolean did_init = falsE;
6745 const char* question;
6746 const mode_t mode = statbuf.st_mode;
6747 if (!S_ISREG(mode))
6748 { e = ( (S_ISDIR(mode)) ? EISDIR : 0 ); goto handle_re_file; }
6749 question = _("File exists - (o)verwrite, (a)ppend, (c)ancel?\n");
6750 if (!did_init) /* must initialize console_oac[] */
6751 { unsigned char c;
6752 const char* src = question;
6753 char ch, *dest = console_oac;
6754 for (c = 0; c <= 2; c++)
6755 { src = my_strchr(src, '(');
6756 if (src == NULL) /* "should not happen" */
6757 { bad_oac: cc_output_errstr(_(strResourceError[reHandshake]));
6758 cc_output_errstr(strNewline); goto cleanup;
6759 }
6760 ch = *++src;
6761 if (!my_islower(ch)) goto bad_oac;
6762 *dest++ = ch;
6763 }
6764 *dest = '\0'; did_init = truE;
6765 }
6766 __dealloc(console_from); console_from = my_strdup(from);
6767 __dealloc(console_to);
6768 if (!must_dealloc_to) console_to = my_strdup(to);
6769 else { console_to = to; must_dealloc_to = falsE; }
6770 cc_output_str(question); console_pk = cpkOverwrite;
6771 }
6772 }
6773 else /* put */
6774 { from = s2; to = s1;
6775 }
6776 cleanup:
6777 if (must_dealloc_to) memory_deallocate(to);
6778 }
6779
6780 static const tCustomConnOps console_ops =
6781 { console_may_handle, console_handle_cccc, console_try_open,
6782 console_do_open, console_do_start, console_do_prepare_sequencer,
6783 console_getput
6784 };
6785
console_input_append(char ch)6786 static void console_input_append(char ch)
6787 { if (console_input_len >= console_input_maxlen)
6788 { console_input_maxlen += 50;
6789 console_input = memory_reallocate(console_input, console_input_maxlen,
6790 mapString);
6791 }
6792 console_input[console_input_len++] = ch;
6793 }
6794
console_handle_res(tResource * resource,unsigned char what,void * _x)6795 static void console_handle_res(tResource* resource, unsigned char what,
6796 void* _x)
6797 { const tConsoleTaskNum task_idx = (tConsoleTaskNum)
6798 MY_POINTER_TO_INT(resource->custconn_handle);
6799 tCustomConnPrintingData* x;
6800 tConsoleTask* task;
6801 tConsoleTaskState state;
6802 const char* text;
6803 tCcSeqEntry* sequencer_entry;
6804 tCcSeqCallback seq_cb;
6805 tBoolean has_seq, stop_seq;
6806 switch (what)
6807 { case 0: /* print something */
6808 if (console_task_curr != task_idx) return; /* user isn't interested */
6809 x = (tCustomConnPrintingData*) _x; text = x->text;
6810 if (x->ccpk == ccpkNetresp)
6811 { /* some text from an untrustable source; might contain arbitrary
6812 rubbish and maybe isn't '\0'-terminated, so let's be careful... */
6813 char ch, netresp[1005];
6814 unsigned char c;
6815 size_t count = x->len, netresplen = 0;
6816 #define netresp_flush do { __netresp_append('\0'); cc_output_str(netresp); } while (0)
6817 #define __netresp_append(ch) netresp[netresplen++] = (ch)
6818 #define netresp_append(ch) do { if (netresplen > 1000) { netresp_flush; netresplen = 0; } __netresp_append(ch); } while (0)
6819 while (count-- > 0)
6820 { ch = *text++; c = (unsigned char) ch;
6821 if ( (!is_bad_uchar(c)) || (ch == '\n') ) netresp_append(ch);
6822 }
6823 if (netresplen > 0) netresp_flush;
6824 #undef netresp_flush
6825 #undef __netresp_append
6826 #undef netresp_append
6827 }
6828 else
6829 { if (x->ccpk == ccpkNetcmd)
6830 { cc_output_str(_("Sending: "));
6831 if (strneqcase(text, "pass ", 5)) text = "PASS...\n"; /* disguise */
6832 }
6833 cc_output_str(text); /* IMPLEMENTME: coloring! */
6834 }
6835 break;
6836 case 1: /* unbusify */
6837 task = &(console_task[task_idx]); state = task->state;
6838 resource->bytecount = task->last_hash_bytecount = 0;
6839 sequencer_entry = &(task->sequencer_entry);
6840 seq_cb = sequencer_entry->callback;
6841 has_seq = cond2boolean(seq_cb != NULL);
6842 if (resource->flags & rfCustomConnStopSequencer)
6843 { stop_seq = truE; resource->flags &= ~rfCustomConnStopSequencer; }
6844 else stop_seq = falsE;
6845 if (state == ctsBusy)
6846 { task->state = ctsPromptable;
6847 resource->state = rsMsgExchange; /* CHECKME! */
6848 if ( (has_seq) && (!stop_seq) && (!(sequencer_entry->did_send)) )
6849 { sequencer_entry->did_send = truE;
6850 (seq_cb)(&(task->context), resource, sequencer_entry, ccssSend);
6851 }
6852 else
6853 { if (has_seq)
6854 cc_sequencer_cleanup(&(task->context), sequencer_entry, resource);
6855 if (console_task_curr == task_idx) console_try_prompt = truE;
6856 }
6857 }
6858 #if CONFIG_DEBUG
6859 else
6860 { /* This "should not happen" unless the server sends a message without a
6861 prior command, e.g. an "idle timeout" message. No problem, nothing
6862 to do here. */
6863 sprint_safe(debugstrbuf, "BUG (maybe): console task state %p,%d,%d\n",
6864 resource, task_idx, state);
6865 debugmsg(debugstrbuf); cc_output_errstr(debugstrbuf);
6866 }
6867 #endif
6868 break;
6869 case 2: /* print "connecting to..." or similar */
6870 if (console_task_curr == task_idx) /* the user might be interested */
6871 { tResourceRequest* request;
6872 const char* message;
6873 tBoolean is_error;
6874 task = &(console_task[task_idx]); request = task->request;
6875 message = calculate_reqresmsg(request, resource, 0, &is_error);
6876 if (*message != '\0')
6877 { cc_output_str(message); cc_output_str(strNewline); }
6878 }
6879 break;
6880 case 3: /* print a connect-related error message */
6881 if (console_task_curr == task_idx) /* the user might be interested */
6882 { const tResourceError* _re = (const tResourceError*) _x;
6883 cc_output_errstr(_(strResourceError[*_re]));
6884 cc_output_errstr(strNewline);
6885 }
6886 break;
6887 case 4: /* print hashmarks */
6888 if ( (cc_do_hash) && (console_task_curr == task_idx) )
6889 { task = &(console_task[task_idx]);
6890 console_print_hashmarks(task, resource);
6891 }
6892 break;
6893 }
6894 if (console_try_prompt) console_print_prompt();
6895 }
6896
console_input_interpret(void)6897 static one_caller void console_input_interpret(void)
6898 { const tConsolePromptKind cpk = console_pk;
6899 if (console_input_len <= 0) return; /* nothing entered, nothing to do */
6900 console_input_append('\0'); /* (for simplicity) */
6901 console_input_len = 0; /* preparation for the next input line */
6902 my_write_str(fd_stdout, strNewline); console_pk = cpkNormal;
6903 if (cpk == cpkPassword)
6904 { const char* arg[1];
6905 arg[0] = console_input; cc_start_cmd(console_currctx, strPass, arg, 1);
6906 }
6907 else if (cpk == cpkOverwrite) console_handle_oac(console_input);
6908 else ccc_execute(console_currctx, console_input);
6909 console_try_prompt = truE;
6910 }
6911
console_keyboard_handler(__sunused void * data __cunused,__sunused tFdObservationFlags flags __cunused)6912 static void console_keyboard_handler(__sunused void* data __cunused,
6913 __sunused tFdObservationFlags flags __cunused)
6914 { char ch;
6915 int err = my_read(fd_keyboard_input, &ch, sizeof(ch));
6916 if (err <= 0) { /* nothing; hope it's just a temporary failure... */ }
6917 else if (!console_prompting)
6918 { if (ch == '&') /* the user wants to get at a prompt */
6919 { console_task_curr = CONSOLE_TASK_INVALID; console_try_prompt = truE; }
6920 }
6921 else if (ch == '\n') console_input_interpret();
6922 else if (ch == config.console_backspace)
6923 { if (console_input_len > 0)
6924 { console_input_len--; my_write_str(fd_stdout, "\b \b"); }
6925 }
6926 else if (console_input_len <= STRBUF_SIZE / 2) /* (just a "sane" limit) */
6927 { const unsigned char c = (unsigned char) ch;
6928 if (!is_bad_uchar(c))
6929 { /* if ( (ch != ' ') || ( (console_input_len > 0) &&
6930 (console_input[console_input_len - 1] != ' ') ) ||
6931 (console_pk == cpkPassword) ) -- CHECKME! */
6932 { console_input_append(ch);
6933 if (console_pk_disguising) ch = '*';
6934 my_write(fd_stdout, &ch, 1);
6935 }
6936 }
6937 }
6938 if (console_try_prompt) console_print_prompt();
6939 }
6940
6941 #endif /* #if CONFIG_CONSOLE */
6942
6943
6944 /** Main entry point */
6945
6946 #if TGC_IS_CURSES
6947
curses_keyboard_handler(__sunused void * data __cunused,__sunused tFdObservationFlags flags __cunused)6948 static void curses_keyboard_handler(__sunused void* data __cunused,
6949 __sunused tFdObservationFlags flags __cunused)
6950 { tKey key = getch();
6951 #if CONFIG_PLATFORM == 1
6952 if (key != 0)
6953 #endif
6954 { if (key != ERR) handle_key(key); }
6955 }
6956
6957 #endif /* #if TGC_IS_CURSES */
6958
6959 #if CAN_HANDLE_SIGNALS
any2main_input_handler(__sunused void * data __cunused,__sunused tFdObservationFlags flags __cunused)6960 static void any2main_input_handler(__sunused void* data __cunused,
6961 __sunused tFdObservationFlags flags __cunused)
6962 { static char buf[4], count = 0;
6963 ssize_t err = my_read_pipe(fd_any2main_read, buf + count, 4 - count);
6964 if (err <= 0) return;
6965 count += err;
6966 if (count < 4) return; /* didn't yet get a complete four-byte sequence */
6967 count = 0; /* preparation for the next "message" */
6968 if (!strncmp(buf, strQuit, 4)) do_quit_sig(); /* terminated by a signal */
6969 #if (TGC_IS_CURSES) && ( (HAVE_CURSES_RESIZETERM) || (defined(resizeterm)) )
6970 else if (*buf == 'w') /* resize the terminal */
6971 { const unsigned char* const temp = (const unsigned char*) buf,
6972 cols = temp[1], rows = temp[2];
6973 if ( (COLS != cols) || (LINES != rows) ) /* need resize */
6974 {
6975 #if CONFIG_MENUS
6976 if (key_handling_mode == khmMenu) cm_cancel();
6977 #endif
6978 (void) resizeterm(rows, cols); window_redraw_all();
6979 if (key_handling_mode == khmLineInput) line_input_resize();
6980 }
6981 }
6982 #endif
6983 #if CONFIG_CONSOLE
6984 else if (!strncmp(buf, strConsoleDiscard, 4)) /* provide a fresh prompt */
6985 { if (!console_prompting) console_task_curr = CONSOLE_TASK_INVALID;
6986 else { console_input_len = 0; console_pk = cpkNormal; } /* discard input */
6987 console_print_prompt();
6988 }
6989 #endif
6990 }
6991 #endif
6992
6993 #if CONFIG_TG == TG_XCURSES
6994
6995 #if MIGHT_USE_SCROLL_BARS
scroll_bar_calc(tRendererData * data)6996 static void scroll_bar_calc(tRendererData* data)
6997 { tLinenumber* _h = data->line_callback_data;
6998 *_h = *_h + 1;
6999 }
7000 #endif
7001
xcurses_confuser(unsigned char a,void * b,void * c)7002 void xcurses_confuser(unsigned char a, void* b, void* c)
7003 /* quick end dirty - you know the Latin abbr. "q.e.d."? :-) */
7004 { switch (a)
7005 { case 0: /* paste text */
7006 if ( (key_handling_mode == khmLineInput) &&
7007 (lid_area(flags) & liafEditable) )
7008 { const char* text = (const char*) b;
7009 unsigned long size = *((const unsigned long*) c);
7010 while (size-- > 0) line_input_paste_char(*text++);
7011 }
7012 break;
7013 #if MIGHT_USE_SCROLL_BARS
7014 case 1: /* handle a program command code */
7015 { static const tProgramCommandCode cv[6] = { pccUnknown, pccLineUp,
7016 pccPageUp, pccUnknown, pccPageDown, pccLineDown };
7017 const unsigned char code = *((const unsigned char*) b);
7018 const tProgramCommandCode pcc = cv[code];
7019 if (pcc != pccUnknown) handle_command_code(pcc);
7020 }
7021 break;
7022 case 2: /* scroll to a specific line */
7023 { tBrowserDocument* document = current_document_x;
7024 if (document != NULL)
7025 { document->origin_y = (tLinenumber) (*((const int*) b));
7026 document_display(document, wrtRedraw);
7027 }
7028 }
7029 break;
7030 case 3: /* calculate scroll bar information */
7031 { tBrowserDocument* document = current_document_x;
7032 int* v = (int*) b;
7033 tLinenumber h;
7034 if ( (document == NULL) || (!document_section_height(document, &h)) )
7035 { v[0] = v[1] = v[2] = 0; return; }
7036 v[0] = (int) h; v[1] = document->origin_y;
7037 if (document->sbvi != COLS) /* must recalculate the document height */
7038 { tLinenumber height = 0;
7039 short mincol, maxcol;
7040 if (document_minmaxcol(document, &mincol, &maxcol))
7041 { tRendererData data;
7042 const short width = maxcol - mincol + 1;
7043 wrc_rd_setup(&data, document, width); data.flags |= rdfVirtual;
7044 data.line_callback_data = &height;
7045 data.line_callback = scroll_bar_calc;
7046 renderer_run(&data); document->sbdh = height; document->sbvi=COLS;
7047 }
7048 }
7049 v[2] = document->sbdh;
7050 }
7051 break;
7052 #endif
7053 }
7054 }
7055
7056 #endif /* #if CONFIG_TG == TG_XCURSES */
7057
7058 #if TGC_IS_WINDOWING
7059 #include "wk.c"
7060 #endif
7061
7062 #if CONFIG_CUSTOM_CONN
main_handle_custom_conn(tResource * resource,unsigned char what,void * _x)7063 void main_handle_custom_conn(tResource* resource, unsigned char what, void* _x)
7064 {
7065 #if CONFIG_CONSOLE
7066 if (program_mode == pmConsole) console_handle_res(resource, what, _x);
7067 else
7068 #endif
7069 { /* handle custom connection window */ }
7070 }
7071 #endif
7072
7073 #if TGC_IS_CURSES
cursor_reset_position(void)7074 static one_caller void cursor_reset_position(void)
7075 { if (key_handling_mode == khmLineInput)
7076 (void) move(lid_area(row), lid_area(colcurr));
7077 else (void) move( (current_window_index_x == 1) ? (VMIDDLE + 1) : 0, 0);
7078 __must_reset_cursor = falsE;
7079 }
7080 #endif
7081
7082 #if CONFIG_PLATFORM == 1
7083 static const_after_init long chStdin;
7084 #endif
7085
main_initialize(void)7086 static one_caller void __init main_initialize(void)
7087 { size_t count;
7088 #if CAN_HANDLE_SIGNALS
7089 fd_observe(fd_any2main_read, any2main_input_handler, NULL, fdofRead);
7090 #endif
7091 sinking_data_reset(); *sfconv = '%'; sfconv[3] = '\0';
7092 #if CONFIG_MENUS & MENUS_UHIST
7093 my_memclr_arr(uri_history);
7094 #endif
7095 #if TGC_IS_GRAPHICS
7096 istrYesUc = my_strdup_ucfirst(_(strYes));
7097 istrNoUc = my_strdup_ucfirst(_(strNo));
7098 default_font =
7099 gdk_font_load("-adobe-helvetica-*-r-normal--*-*-*-*-*-*-iso8859-1");
7100 #endif
7101
7102 #if CONFIG_PLATFORM == 1
7103 if (is_promptable) chStdin = fgetchid(stdin);
7104 #endif
7105 if (!is_environed) return; /* forget the rest */
7106 for (count = 0; count < ARRAY_ELEMNUM(keymap_command_defaultkeys); count++)
7107 { const tKeymapCommandEntry* x = &(keymap_command_defaultkeys[count]);
7108 (void) keymap_command_key_do_register(x->key, x->pcc);
7109 }
7110 qsort(keymap_command_keys, keymap_command_keys_num,
7111 sizeof(tKeymapCommandEntry), keymap_command_sorter);
7112 /* (The qsort() might be slow because the defaultkeys[] was already "quite
7113 sorted"; don't care much - it's only done once, and only at program
7114 initialization time, and the array isn't _that_ big:-) */
7115 for (count = 0; count < ARRAY_ELEMNUM(keymap_lineinput_defaultkeys); count++)
7116 { const tKeymapLineinputEntry* x = &(keymap_lineinput_defaultkeys[count]);
7117 (void) keymap_lineinput_key_do_register(x->key, x->liac);
7118 }
7119 qsort(keymap_lineinput_keys, keymap_lineinput_keys_num,
7120 sizeof(tKeymapLineinputEntry), keymap_lineinput_sorter);
7121 }
7122
main(int argc,const char ** argv)7123 int main(int argc, const char** argv)
7124 { /* getting start(l)ed... */
7125 initialize(argc, argv);
7126 main_initialize();
7127
7128 if (is_environed)
7129 { unsigned char count;
7130 #if CONFIG_SESSIONS
7131 { const char* filename = config.session_resume;
7132 if (filename != NULL) session_resume(filename);
7133 }
7134 if (windowlisthead == NULL) /* didn't actually resume anything */
7135 #endif
7136 { if (launch_uri_count == 0)
7137 { /* The user didn't provide any URI on the command-line, so we use a
7138 default URI. We don't use the "home" URI because that might cause
7139 undesired network activity (which might cost money although the user
7140 maybe launched retawq only to view some local files); the user can
7141 go there by pressing "h", and I think that's acceptable. :-) */
7142 launch_uri[launch_uri_count++] = "about:retawq";
7143 }
7144 }
7145 for (count = 0; count < launch_uri_count; count++)
7146 { tWindow* w = wk_browser_create();
7147 #if TGC_IS_CURSES
7148 if (count < 2) visible_window_x[count] = w;
7149 #endif
7150 wk_browser_prr(w, launch_uri[count], prrfNone, NULL);
7151 }
7152 window_redraw_all();
7153 }
7154 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
7155 else if (program_mode == pmDownload)
7156 content_download_to_fd(config.pm_uri, NULL, 1);
7157 #endif
7158 #if CONFIG_EXTRA & EXTRA_DUMP
7159 else if (program_mode == pmDump) content_dump_auto(config.pm_uri);
7160 #endif
7161 #if CONFIG_CONSOLE
7162 else if (program_mode == pmConsole)
7163 { cc_output_str(strCopyright); cc_output_str(strProgramLegalese);
7164 if (initial_console_msgs != NULL)
7165 { cc_output_str(strNewline); cc_output_str(initial_console_msgs); }
7166 cc_output_str(_("\nEntering console runmode. Use the command \"help\" for a list of commands.\n"));
7167 console_print_prompt();
7168 }
7169 dealloc(initial_console_msgs);
7170 #endif
7171
7172 #if TGC_IS_CURSES
7173
7174 #if CONFIG_PLATFORM != 1
7175 if (is_environed)
7176 fd_observe(fd_keyboard_input, curses_keyboard_handler, NULL, fdofRead);
7177 #if CONFIG_CONSOLE
7178 else if (program_mode == pmConsole)
7179 fd_observe(fd_keyboard_input, console_keyboard_handler, NULL, fdofRead);
7180 #endif
7181 #endif
7182
7183 while (1)
7184 { remaining_work_do();
7185 if (is_environed)
7186 { if (__must_reset_cursor) cursor_reset_position();
7187 (void) refresh();
7188 }
7189 i18n_cleanup
7190 fd_multiplex(); /* try to get some sleep :-) */
7191 /* Now disturb all the nice abstraction for very special cases... */
7192 #if CONFIG_PLATFORM == 1
7193 if ( (is_promptable) && (io_pend(chStdin, 0) == 0) )
7194 {
7195 #if CONFIG_CONSOLE
7196 if (program_mode == pmConsole)
7197 { /* console_keyboard_handler(NULL, 0); -- CHECKME: what instead? */ }
7198 else
7199 #endif
7200 { curses_keyboard_handler(NULL, 0); }
7201 }
7202 #elif CONFIG_TG == TG_BICURSES
7203 { /* This is necessary e.g. if the user just presses the Escape key and
7204 nothing further - the fd callback isn't executed further, so the
7205 timeout couldn't be detected. */
7206 tKey key = my_builtin_getch(falsE);
7207 if (key != ERR) handle_key(key);
7208 }
7209 #endif
7210 }
7211
7212 #elif CONFIG_TG == TG_GTK
7213
7214 if (is_environed) { need_tglib_cleanup = truE; gtk_main(); }
7215 else { while (1) { i18n_cleanup fd_multiplex(); } }
7216
7217 #endif /* tg */
7218
7219 /*@notreached@*/ do_quit(); /*@notreached@*/
7220 return(0); /* avoid compiler warning... */
7221 }
7222