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