OSDN Git Service

Modify documents for 1.98d.
[ffftp/ffftp.git] / contrib / putty / MACOSX / OSXDLG.M
1 /*\r
2  * osxdlg.m: various PuTTY dialog boxes for OS X.\r
3  */\r
4 \r
5 #import <Cocoa/Cocoa.h>\r
6 #include "putty.h"\r
7 #include "storage.h"\r
8 #include "dialog.h"\r
9 #include "osxclass.h"\r
10 \r
11 /*\r
12  * The `ConfigWindow' class is used to start up a new PuTTY\r
13  * session.\r
14  */\r
15 \r
16 @class ConfigTree;\r
17 @interface ConfigTree : NSObject\r
18 {\r
19     NSString **paths;\r
20     int *levels;\r
21     int nitems, itemsize;\r
22 }\r
23 - (void)addPath:(char *)path;\r
24 @end\r
25 \r
26 @implementation ConfigTree\r
27 - (id)init\r
28 {\r
29     self = [super init];\r
30     paths = NULL;\r
31     levels = NULL;\r
32     nitems = itemsize = 0;\r
33     return self;\r
34 }\r
35 - (void)addPath:(char *)path\r
36 {\r
37     if (nitems >= itemsize) {\r
38         itemsize += 32;\r
39         paths = sresize(paths, itemsize, NSString *);\r
40         levels = sresize(levels, itemsize, int);\r
41     }\r
42     paths[nitems] = [[NSString stringWithCString:path] retain];\r
43     levels[nitems] = ctrl_path_elements(path) - 1;\r
44     nitems++;\r
45 }\r
46 - (void)dealloc\r
47 {\r
48     int i;\r
49 \r
50     for (i = 0; i < nitems; i++)\r
51         [paths[i] release];\r
52 \r
53     sfree(paths);\r
54     sfree(levels);\r
55 \r
56     [super dealloc];\r
57 }\r
58 - (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count\r
59 {\r
60     int i, plevel;\r
61 \r
62     if (item) {\r
63         for (i = 0; i < nitems; i++)\r
64             if (paths[i] == item)\r
65                 break;\r
66         assert(i < nitems);\r
67         plevel = levels[i];\r
68         i++;\r
69     } else {\r
70         i = 0;\r
71         plevel = -1;\r
72     }\r
73 \r
74     if (count)\r
75         *count = 0;\r
76 \r
77     while (index > 0) {\r
78         if (i >= nitems || levels[i] != plevel+1)\r
79             return nil;\r
80         if (count)\r
81             (*count)++;\r
82         do {\r
83             i++;\r
84         } while (i < nitems && levels[i] > plevel+1);\r
85         index--;\r
86     }\r
87 \r
88     return paths[i];\r
89 }\r
90 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item\r
91 {\r
92     return [self iterateChildren:index ofItem:item count:NULL];\r
93 }\r
94 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item\r
95 {\r
96     int count = 0;\r
97     /* pass nitems+1 to ensure we run off the end */\r
98     [self iterateChildren:nitems+1 ofItem:item count:&count];\r
99     return count;\r
100 }\r
101 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item\r
102 {\r
103     return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;\r
104 }\r
105 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item\r
106 {\r
107     /*\r
108      * Trim off all path elements except the last one.\r
109      */\r
110     NSArray *components = [item componentsSeparatedByString:@"/"];\r
111     return [components objectAtIndex:[components count]-1];\r
112 }\r
113 @end\r
114 \r
115 @implementation ConfigWindow\r
116 - (id)initWithConfig:(Config)aCfg\r
117 {\r
118     NSScrollView *scrollview;\r
119     NSTableColumn *col;\r
120     ConfigTree *treedata;\r
121     int by = 0, mby = 0;\r
122     int wmin = 0;\r
123     int hmin = 0;\r
124     int panelht = 0;\r
125 \r
126     ctrlbox = ctrl_new_box();\r
127     setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol,\r
128                      0 /* protcfginfo */);\r
129     unix_setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol);\r
130 \r
131     cfg = aCfg;                        /* structure copy */\r
132 \r
133     self = [super initWithContentRect:NSMakeRect(0,0,300,300)\r
134             styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |\r
135                        NSClosableWindowMask)\r
136             backing:NSBackingStoreBuffered\r
137             defer:YES];\r
138     [self setTitle:@"PuTTY Configuration"];\r
139 \r
140     [self setIgnoresMouseEvents:NO];\r
141 \r
142     dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));\r
143 \r
144     scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];\r
145     treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];\r
146     [scrollview setBorderType:NSLineBorder];\r
147     [scrollview setDocumentView:treeview];\r
148     [[self contentView] addSubview:scrollview];\r
149     [scrollview setHasVerticalScroller:YES];\r
150     [scrollview setAutohidesScrollers:YES];\r
151     /* FIXME: the below is untested. Test it then remove this notice. */\r
152     [treeview setAllowsColumnReordering:NO];\r
153     [treeview setAllowsColumnResizing:NO];\r
154     [treeview setAllowsMultipleSelection:NO];\r
155     [treeview setAllowsEmptySelection:NO];\r
156     [treeview setAllowsColumnSelection:YES];\r
157 \r
158     treedata = [[[ConfigTree alloc] init] retain];\r
159 \r
160     col = [[NSTableColumn alloc] initWithIdentifier:nil];\r
161     [treeview addTableColumn:col];\r
162     [treeview setOutlineTableColumn:col];\r
163 \r
164     [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];\r
165 \r
166     /*\r
167      * Create the controls.\r
168      */\r
169     {\r
170         int i;\r
171         char *path = NULL;\r
172 \r
173         for (i = 0; i < ctrlbox->nctrlsets; i++) {\r
174             struct controlset *s = ctrlbox->ctrlsets[i];\r
175             int mw, mh;\r
176 \r
177             if (!*s->pathname) {\r
178 \r
179                 create_ctrls(dv, [self contentView], s, &mw, &mh);\r
180 \r
181                 by += 20 + mh;\r
182 \r
183                 if (wmin < mw + 40)\r
184                     wmin = mw + 40;\r
185             } else {\r
186                 int j = path ? ctrl_path_compare(s->pathname, path) : 0;\r
187 \r
188                 if (j != INT_MAX) {    /* add to treeview, start new panel */\r
189                     char *c;\r
190 \r
191                     /*\r
192                      * We expect never to find an implicit path\r
193                      * component. For example, we expect never to\r
194                      * see A/B/C followed by A/D/E, because that\r
195                      * would _implicitly_ create A/D. All our path\r
196                      * prefixes are expected to contain actual\r
197                      * controls and be selectable in the treeview;\r
198                      * so we would expect to see A/D _explicitly_\r
199                      * before encountering A/D/E.\r
200                      */\r
201                     assert(j == ctrl_path_elements(s->pathname) - 1);\r
202 \r
203                     c = strrchr(s->pathname, '/');\r
204                     if (!c)\r
205                         c = s->pathname;\r
206                     else\r
207                         c++;\r
208 \r
209                     [treedata addPath:s->pathname];\r
210                     path = s->pathname;\r
211 \r
212                     panelht = 0;\r
213                 }\r
214 \r
215                 create_ctrls(dv, [self contentView], s, &mw, &mh);\r
216                 if (wmin < mw + 3*20+150)\r
217                     wmin = mw + 3*20+150;\r
218                 panelht += mh + 20;\r
219                 if (hmin < panelht - 20)\r
220                     hmin = panelht - 20;\r
221             }\r
222         }\r
223     }\r
224 \r
225     {\r
226         int i;\r
227         NSRect r;\r
228 \r
229         [treeview setDataSource:treedata];\r
230         for (i = [treeview numberOfRows]; i-- ;)\r
231             [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];\r
232 \r
233         [treeview sizeToFit];\r
234         r = [treeview frame];\r
235         if (hmin < r.size.height)\r
236             hmin = r.size.height;\r
237     }\r
238 \r
239     [self setContentSize:NSMakeSize(wmin, hmin+60+by)];\r
240     [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];\r
241     [treeview setDelegate:self];\r
242     mby = by;\r
243 \r
244     /*\r
245      * Now place the controls.\r
246      */\r
247     {\r
248         int i;\r
249         char *path = NULL;\r
250         panelht = 0;\r
251 \r
252         for (i = 0; i < ctrlbox->nctrlsets; i++) {\r
253             struct controlset *s = ctrlbox->ctrlsets[i];\r
254 \r
255             if (!*s->pathname) {\r
256                 by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);\r
257             } else {\r
258                 if (!path || strcmp(s->pathname, path))\r
259                     panelht = 0;\r
260 \r
261                 panelht += VSPACING + place_ctrls(dv, s, 2*20+150,\r
262                                                   40+mby+hmin-panelht,\r
263                                                   wmin - (3*20+150));\r
264 \r
265                 path = s->pathname;\r
266             }\r
267         }\r
268     }\r
269 \r
270     select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);\r
271 \r
272     [treeview reloadData];\r
273 \r
274     dlg_refresh(NULL, dv);\r
275 \r
276     [self center];                     /* :-) */\r
277 \r
278     return self;\r
279 }\r
280 - (void)configBoxFinished:(id)object\r
281 {\r
282     int ret = [object intValue];       /* it'll be an NSNumber */\r
283     if (ret) {\r
284         [controller performSelectorOnMainThread:\r
285          @selector(newSessionWithConfig:)\r
286          withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]\r
287          waitUntilDone:NO];\r
288     }\r
289     [self close];\r
290 }\r
291 - (void)outlineViewSelectionDidChange:(NSNotification *)notification\r
292 {\r
293     const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];\r
294     select_panel(dv, ctrlbox, path);\r
295 }\r
296 - (BOOL)outlineView:(NSOutlineView *)outlineView\r
297     shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item\r
298 {\r
299     return NO;                         /* no editing! */\r
300 }\r
301 @end\r
302 \r
303 /* ----------------------------------------------------------------------\r
304  * Various special-purpose dialog boxes.\r
305  */\r
306 \r
307 struct appendstate {\r
308     void (*callback)(void *ctx, int result);\r
309     void *ctx;\r
310 };\r
311 \r
312 static void askappend_callback(void *ctx, int result)\r
313 {\r
314     struct appendstate *state = (struct appendstate *)ctx;\r
315 \r
316     state->callback(state->ctx, (result == NSAlertFirstButtonReturn ? 2 :\r
317                                  result == NSAlertSecondButtonReturn ? 1 : 0));\r
318     sfree(state);\r
319 }\r
320 \r
321 int askappend(void *frontend, Filename filename,\r
322               void (*callback)(void *ctx, int result), void *ctx)\r
323 {\r
324     static const char msgtemplate[] =\r
325         "The session log file \"%s\" already exists. "\r
326         "You can overwrite it with a new session log, "\r
327         "append your session log to the end of it, "\r
328         "or disable session logging for this session.";\r
329 \r
330     char *text;\r
331     SessionWindow *win = (SessionWindow *)frontend;\r
332     struct appendstate *state;\r
333     NSAlert *alert;\r
334 \r
335     text = dupprintf(msgtemplate, filename.path);\r
336 \r
337     state = snew(struct appendstate);\r
338     state->callback = callback;\r
339     state->ctx = ctx;\r
340 \r
341     alert = [[NSAlert alloc] init];\r
342     [alert setInformativeText:[NSString stringWithCString:text]];\r
343     [alert addButtonWithTitle:@"Overwrite"];\r
344     [alert addButtonWithTitle:@"Append"];\r
345     [alert addButtonWithTitle:@"Disable"];\r
346     [win startAlert:alert withCallback:askappend_callback andCtx:state];\r
347 \r
348     return -1;\r
349 }\r
350 \r
351 struct algstate {\r
352     void (*callback)(void *ctx, int result);\r
353     void *ctx;\r
354 };\r
355 \r
356 static void askalg_callback(void *ctx, int result)\r
357 {\r
358     struct algstate *state = (struct algstate *)ctx;\r
359 \r
360     state->callback(state->ctx, result == NSAlertFirstButtonReturn);\r
361     sfree(state);\r
362 }\r
363 \r
364 int askalg(void *frontend, const char *algtype, const char *algname,\r
365            void (*callback)(void *ctx, int result), void *ctx)\r
366 {\r
367     static const char msg[] =\r
368         "The first %s supported by the server is "\r
369         "%s, which is below the configured warning threshold.\n"\r
370         "Continue with connection?";\r
371 \r
372     char *text;\r
373     SessionWindow *win = (SessionWindow *)frontend;\r
374     struct algstate *state;\r
375     NSAlert *alert;\r
376 \r
377     text = dupprintf(msg, algtype, algname);\r
378 \r
379     state = snew(struct algstate);\r
380     state->callback = callback;\r
381     state->ctx = ctx;\r
382 \r
383     alert = [[NSAlert alloc] init];\r
384     [alert setInformativeText:[NSString stringWithCString:text]];\r
385     [alert addButtonWithTitle:@"Yes"];\r
386     [alert addButtonWithTitle:@"No"];\r
387     [win startAlert:alert withCallback:askalg_callback andCtx:state];\r
388 \r
389     return -1;\r
390 }\r
391 \r
392 struct hostkeystate {\r
393     char *host, *keytype, *keystr;\r
394     int port;\r
395     void (*callback)(void *ctx, int result);\r
396     void *ctx;\r
397 };\r
398 \r
399 static void verify_ssh_host_key_callback(void *ctx, int result)\r
400 {\r
401     struct hostkeystate *state = (struct hostkeystate *)ctx;\r
402 \r
403     if (result == NSAlertThirdButtonReturn)   /* `Accept' */\r
404         store_host_key(state->host, state->port,\r
405                        state->keytype, state->keystr);\r
406     state->callback(state->ctx, result != NSAlertFirstButtonReturn);\r
407     sfree(state->host);\r
408     sfree(state->keytype);\r
409     sfree(state->keystr);\r
410     sfree(state);\r
411 }\r
412 \r
413 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,\r
414                         char *keystr, char *fingerprint,\r
415                         void (*callback)(void *ctx, int result), void *ctx)\r
416 {\r
417     static const char absenttxt[] =\r
418         "The server's host key is not cached. You have no guarantee "\r
419         "that the server is the computer you think it is.\n"\r
420         "The server's %s key fingerprint is:\n"\r
421         "%s\n"\r
422         "If you trust this host, press \"Accept\" to add the key to "\r
423         "PuTTY's cache and carry on connecting.\n"\r
424         "If you want to carry on connecting just once, without "\r
425         "adding the key to the cache, press \"Connect Once\".\n"\r
426         "If you do not trust this host, press \"Cancel\" to abandon the "\r
427         "connection.";\r
428     static const char wrongtxt[] =\r
429         "WARNING - POTENTIAL SECURITY BREACH!\n"\r
430         "The server's host key does not match the one PuTTY has "\r
431         "cached. This means that either the server administrator "\r
432         "has changed the host key, or you have actually connected "\r
433         "to another computer pretending to be the server.\n"\r
434         "The new %s key fingerprint is:\n"\r
435         "%s\n"\r
436         "If you were expecting this change and trust the new key, "\r
437         "press \"Accept\" to update PuTTY's cache and continue connecting.\n"\r
438         "If you want to carry on connecting but without updating "\r
439         "the cache, press \"Connect Once\".\n"\r
440         "If you want to abandon the connection completely, press "\r
441         "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "\r
442         "safe choice.";\r
443 \r
444     int ret;\r
445     char *text;\r
446     SessionWindow *win = (SessionWindow *)frontend;\r
447     struct hostkeystate *state;\r
448     NSAlert *alert;\r
449 \r
450     /*\r
451      * Verify the key.\r
452      */\r
453     ret = verify_host_key(host, port, keytype, keystr);\r
454 \r
455     if (ret == 0)\r
456         return 1;\r
457 \r
458     text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);\r
459 \r
460     state = snew(struct hostkeystate);\r
461     state->callback = callback;\r
462     state->ctx = ctx;\r
463     state->host = dupstr(host);\r
464     state->port = port;\r
465     state->keytype = dupstr(keytype);\r
466     state->keystr = dupstr(keystr);\r
467 \r
468     alert = [[NSAlert alloc] init];\r
469     [alert setInformativeText:[NSString stringWithCString:text]];\r
470     [alert addButtonWithTitle:@"Cancel"];\r
471     [alert addButtonWithTitle:@"Connect Once"];\r
472     [alert addButtonWithTitle:@"Accept"];\r
473     [win startAlert:alert withCallback:verify_ssh_host_key_callback\r
474      andCtx:state];\r
475 \r
476     return -1;\r
477 }\r
478 \r
479 void old_keyfile_warning(void)\r
480 {\r
481     /*\r
482      * This should never happen on OS X. We hope.\r
483      */\r
484 }\r
485 \r
486 static void connection_fatal_callback(void *ctx, int result)\r
487 {\r
488     SessionWindow *win = (SessionWindow *)ctx;\r
489 \r
490     [win endSession:FALSE];\r
491 }\r
492 \r
493 void connection_fatal(void *frontend, char *p, ...)\r
494 {\r
495     SessionWindow *win = (SessionWindow *)frontend;\r
496     va_list ap;\r
497     char *msg;\r
498     NSAlert *alert;\r
499 \r
500     va_start(ap, p);\r
501     msg = dupvprintf(p, ap);\r
502     va_end(ap);\r
503 \r
504     alert = [[NSAlert alloc] init];\r
505     [alert setInformativeText:[NSString stringWithCString:msg]];\r
506     [alert addButtonWithTitle:@"Proceed"];\r
507     [win startAlert:alert withCallback:connection_fatal_callback\r
508      andCtx:win];\r
509 }\r