]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSWindows/DNSServiceBrowser/Windows/Sources/ChooserDialog.cpp
mDNSResponder-258.13.tar.gz
[apple/mdnsresponder.git] / mDNSWindows / DNSServiceBrowser / Windows / Sources / ChooserDialog.cpp
1 /* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #include <assert.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <time.h>
23
24 #include <algorithm>
25 #include <memory>
26
27 #include "stdafx.h"
28
29 #include "DNSServices.h"
30
31 #include "Application.h"
32 #include "AboutDialog.h"
33 #include "LoginDialog.h"
34 #include "Resource.h"
35
36 #include "ChooserDialog.h"
37
38 #ifdef _DEBUG
39 #define new DEBUG_NEW
40 #undef THIS_FILE
41 static char THIS_FILE[] = __FILE__;
42 #endif
43
44 #if 0
45 #pragma mark == Constants ==
46 #endif
47
48 //===========================================================================================================================
49 // Constants
50 //===========================================================================================================================
51
52 // Menus
53
54 enum
55 {
56 kChooserMenuIndexFile = 0,
57 kChooserMenuIndexHelp = 1
58 };
59
60 // Domain List
61
62 #define kDomainListDefaultDomainColumnWidth 164
63
64 // Service List
65
66 #define kServiceListDefaultServiceColumnTypeWidth 146
67 #define kServiceListDefaultServiceColumnDescWidth 230
68
69 // Chooser List
70
71 #define kChooserListDefaultNameColumnWidth 190
72 #define kChooserListDefaultIPColumnWidth 120
73
74 // Windows User Messages
75
76 #define WM_USER_DOMAIN_ADD ( WM_USER + 0x100 )
77 #define WM_USER_DOMAIN_REMOVE ( WM_USER + 0x101 )
78 #define WM_USER_SERVICE_ADD ( WM_USER + 0x102 )
79 #define WM_USER_SERVICE_REMOVE ( WM_USER + 0x103 )
80 #define WM_USER_RESOLVE ( WM_USER + 0x104 )
81
82 #if 0
83 #pragma mark == Constants - Service Table ==
84 #endif
85
86 //===========================================================================================================================
87 // Constants - Service Table
88 //===========================================================================================================================
89
90 struct KnownServiceEntry
91 {
92 const char * serviceType;
93 const char * description;
94 const char * urlScheme;
95 bool useText;
96 };
97
98 static const KnownServiceEntry kKnownServiceTable[] =
99 {
100 { "_accountedge._tcp.", "MYOB AccountEdge", "", false },
101 { "_aecoretech._tcp.", "Apple Application Engineering Services", "", false },
102 { "_afpovertcp._tcp.", "Apple File Sharing (AFP)", "afp://", false },
103 { "_airport._tcp.", "AirPort Base Station", "", false },
104 { "_apple-sasl._tcp.", "Apple Password Server", "", false },
105 { "_aquamon._tcp.", "AquaMon", "", false },
106 { "_async._tcp", "address-o-sync", "", false },
107 { "_auth._tcp.", "Authentication Service", "", false },
108 { "_bootps._tcp.", "Bootstrap Protocol Server", "", false },
109 { "_bousg._tcp.", "Bag Of Unusual Strategy Games", "", false },
110 { "_browse._udp.", "DNS Service Discovery", "", false },
111 { "_cheat._tcp.", "The Cheat", "", false },
112 { "_chess._tcp", "Project Gridlock", "", false },
113 { "_chfts._tcp", "Fluid Theme Server", "", false },
114 { "_clipboard._tcp", "Clipboard Sharing", "", false },
115 { "_contactserver._tcp.", "Now Up-to-Date & Contact", "", false },
116 { "_cvspserver._tcp", "CVS PServer", "", false },
117 { "_cytv._tcp.", "CyTV Network streaming for Elgato EyeTV", "", false },
118 { "_daap._tcp.", "Digital Audio Access Protocol (iTunes)", "daap://", false },
119 { "_distcc._tcp", "Distributed Compiler", "", false },
120 { "_dns-sd._udp", "DNS Service Discovery", "", false },
121 { "_dpap._tcp.", "Digital Picture Access Protocol (iPhoto)", "", false },
122 { "_earphoria._tcp.", "Earphoria", "", false },
123 { "_ecbyesfsgksc._tcp.", "Net Monitor Anti-Piracy Service", "", false },
124 { "_eheap._tcp.", "Interactive Room Software", "", false },
125 { "_embrace._tcp.", "DataEnvoy", "", false },
126 { "_eppc._tcp.", "Remote AppleEvents", "eppc://", false },
127 { "_exec._tcp.", "Remote Process Execution", "", false },
128 { "_facespan._tcp.", "FaceSpan", "", false },
129 { "_fjork._tcp.", "Fjork", "", false },
130 { "_ftp._tcp.", "File Transfer (FTP)", "ftp://", false },
131 { "_ftpcroco._tcp.", "Crocodile FTP Server", "", false },
132 { "_gbs-smp._tcp.", "SnapMail", "", false },
133 { "_gbs-stp._tcp.", "SnapTalk", "", false },
134 { "_grillezvous._tcp.", "Roxio ToastAnywhere(tm) Recorder Sharing", "", false },
135 { "_h323._tcp.", "H.323", "", false },
136 { "_hotwayd._tcp", "Hotwayd", "", false },
137 { "_http._tcp.", "Web Server (HTTP)", "http://", true },
138 { "_hydra._tcp", "SubEthaEdit", "", false },
139 { "_ica-networking._tcp.", "Image Capture Networking", "", false },
140 { "_ichalkboard._tcp.", "iChalk", "", false },
141 { "_ichat._tcp.", "iChat", "ichat://", false },
142 { "_iconquer._tcp.", "iConquer", "", false },
143 { "_imap._tcp.", "Internet Message Access Protocol", "", false },
144 { "_imidi._tcp.", "iMidi", "", false },
145 { "_ipp._tcp.", "Printer (IPP)", "ipp://", false },
146 { "_ishare._tcp.", "iShare", "", false },
147 { "_isparx._tcp.", "iSparx", "", false },
148 { "_istorm._tcp", "iStorm", "", false },
149 { "_iwork._tcp.", "iWork Server", "", false },
150 { "_liaison._tcp.", "Liaison", "", false },
151 { "_login._tcp.", "Remote Login a la Telnet", "", false },
152 { "_lontalk._tcp.", "LonTalk over IP (ANSI 852)", "", false },
153 { "_lonworks._tcp.", "Echelon LNS Remote Client", "", false },
154 { "_macfoh-remote._tcp.", "MacFOH Remote", "", false },
155 { "_moneyworks._tcp.", "MoneyWorks", "", false },
156 { "_mp3sushi._tcp", "MP3 Sushi", "", false },
157 { "_mttp._tcp.", "MenuTunes Sharing", "", false },
158 { "_ncbroadcast._tcp.", "Network Clipboard Broadcasts", "", false },
159 { "_ncdirect._tcp.", "Network Clipboard Direct Transfers", "", false },
160 { "_ncsyncserver._tcp.", "Network Clipboard Sync Server", "", false },
161 { "_newton-dock._tcp.", "Escale", "", false },
162 { "_nfs._tcp", "NFS", "", false },
163 { "_nssocketport._tcp.", "DO over NSSocketPort", "", false },
164 { "_omni-bookmark._tcp.", "OmniWeb", "", false },
165 { "_openbase._tcp.", "OpenBase SQL", "", false },
166 { "_p2pchat._tcp.", "Peer-to-Peer Chat", "", false },
167 { "_pdl-datastream._tcp.", "Printer (PDL)", "pdl://", false },
168 { "_poch._tcp.", "Parallel OperatiOn and Control Heuristic", "", false },
169 { "_pop_2_ambrosia._tcp.", "Pop-Pop", "", false },
170 { "_pop3._tcp", "POP3 Server", "", false },
171 { "_postgresql._tcp", "PostgreSQL Server", "", false },
172 { "_presence._tcp", "iChat AV", "", false },
173 { "_printer._tcp.", "Printer (LPR)", "lpr://", false },
174 { "_ptp._tcp.", "Picture Transfer (PTP)", "ptp://", false },
175 { "_register._tcp", "DNS Service Discovery", "", false },
176 { "_rfb._tcp.", "Remote Frame Buffer", "", false },
177 { "_riousbprint._tcp.", "Remote I/O USB Printer Protocol", "", false },
178 { "_rtsp._tcp.", "Real Time Stream Control Protocol", "", false },
179 { "_safarimenu._tcp", "Safari Menu", "", false },
180 { "_scone._tcp", "Scone", "", false },
181 { "_sdsharing._tcp.", "Speed Download", "", false },
182 { "_seeCard._tcp.", "seeCard", "", false },
183 { "_services._udp.", "DNS Service Discovery", "", false },
184 { "_shell._tcp.", "like exec, but automatic authentication", "", false },
185 { "_shout._tcp.", "Shout", "", false },
186 { "_shoutcast._tcp", "Nicecast", "", false },
187 { "_smb._tcp.", "Windows File Sharing (SMB)", "smb://", false },
188 { "_soap._tcp.", "Simple Object Access Protocol", "", false },
189 { "_spincrisis._tcp.", "Spin Crisis", "", false },
190 { "_spl-itunes._tcp.", "launchTunes", "", false },
191 { "_spr-itunes._tcp.", "netTunes", "", false },
192 { "_ssh._tcp.", "Secure Shell (SSH)", "ssh://", false },
193 { "_ssscreenshare._tcp", "Screen Sharing", "", false },
194 { "_sge-exec._tcp", "Sun Grid Engine (Execution Host)", "", false },
195 { "_sge-qmaster._tcp", "Sun Grid Engine (Master)", "", false },
196 { "_stickynotes._tcp", "Sticky Notes", "", false },
197 { "_strateges._tcp", "Strateges", "", false },
198 { "_sxqdea._tcp", "Synchronize! Pro X", "", false },
199 { "_sybase-tds._tcp", "Sybase Server", "", false },
200 { "_tce._tcp", "Power Card", "", false },
201 { "_teamlist._tcp", "ARTIS Team Task", "", false },
202 { "_teleport._tcp", "teleport", "", false },
203 { "_telnet._tcp.", "Telnet", "telnet://", false },
204 { "_tftp._tcp.", "Trivial File Transfer (TFTP)", "tftp://", false },
205 { "_tinavigator._tcp.", "TI Navigator", "", false },
206 { "_tivo_servemedia._tcp", "TiVo", "", false },
207 { "_upnp._tcp.", "Universal Plug and Play", "", false },
208 { "_utest._tcp.", "uTest", "", false },
209 { "_vue4rendercow._tcp", "VueProRenderCow", "", false },
210 { "_webdav._tcp.", "WebDAV", "webdav://", false },
211 { "_whamb._tcp.", "Whamb", "", false },
212 { "_workstation._tcp", "Macintosh Manager", "", false },
213 { "_ws._tcp", "Web Services", "", false },
214 { "_xserveraid._tcp.", "Xserve RAID", "xsr://", false },
215 { "_xsync._tcp.", "Xserve RAID Synchronization", "", false },
216
217 { "", "", "", false },
218
219 // Unofficial and invalid service types that will be phased out:
220
221 { "_clipboardsharing._tcp.", "ClipboardSharing", "", false },
222 { "_MacOSXDupSuppress._tcp.", "Mac OS X Duplicate Suppression", "", false },
223 { "_netmonitorserver._tcp.", "Net Monitor Server", "", false },
224 { "_networkclipboard._tcp.", "Network Clipboard", "", false },
225 { "_slimdevices_slimp3_cli._tcp.", "SliMP3 Server Command-Line Interface", "", false },
226 { "_slimdevices_slimp3_http._tcp.", "SliMP3 Server Web Interface", "", false },
227 { "_tieducationalhandhelddevice._tcp.", "TI Connect Manager", "", false },
228 { "_tivo_servemedia._tcp.", "TiVo", "", false },
229
230 { NULL, NULL, NULL, false },
231 };
232
233 #if 0
234 #pragma mark == Structures ==
235 #endif
236
237 //===========================================================================================================================
238 // Structures
239 //===========================================================================================================================
240
241 struct DomainEventInfo
242 {
243 DNSBrowserEventType eventType;
244 CString domain;
245 DNSNetworkAddress ifIP;
246 };
247
248 struct ServiceEventInfo
249 {
250 DNSBrowserEventType eventType;
251 std::string name;
252 std::string type;
253 std::string domain;
254 DNSNetworkAddress ifIP;
255 };
256
257 #if 0
258 #pragma mark == Prototypes ==
259 #endif
260
261 //===========================================================================================================================
262 // Prototypes
263 //===========================================================================================================================
264
265 static void
266 BrowserCallBack(
267 void * inContext,
268 DNSBrowserRef inRef,
269 DNSStatus inStatusCode,
270 const DNSBrowserEvent * inEvent );
271
272 static char * DNSNetworkAddressToString( const DNSNetworkAddress *inAddr, char *outString );
273
274 static DWORD UTF8StringToStringObject( const char *inUTF8, CString &inObject );
275 static DWORD StringObjectToUTF8String( CString &inObject, std::string &outUTF8 );
276
277 #if 0
278 #pragma mark == Message Map ==
279 #endif
280
281 //===========================================================================================================================
282 // Message Map
283 //===========================================================================================================================
284
285 BEGIN_MESSAGE_MAP(ChooserDialog, CDialog)
286 //{{AFX_MSG_MAP(ChooserDialog)
287 ON_WM_SYSCOMMAND()
288 ON_NOTIFY(LVN_ITEMCHANGED, IDC_DOMAIN_LIST, OnDomainListChanged)
289 ON_NOTIFY(LVN_ITEMCHANGED, IDC_SERVICE_LIST, OnServiceListChanged)
290 ON_NOTIFY(LVN_ITEMCHANGED, IDC_CHOOSER_LIST, OnChooserListChanged)
291 ON_NOTIFY(NM_DBLCLK, IDC_CHOOSER_LIST, OnChooserListDoubleClick)
292 ON_COMMAND(ID_HELP_ABOUT, OnAbout)
293 ON_WM_INITMENUPOPUP()
294 ON_WM_ACTIVATE()
295 ON_COMMAND(ID_FILE_CLOSE, OnFileClose)
296 ON_COMMAND(ID_FILE_EXIT, OnExit)
297 ON_WM_CLOSE()
298 ON_WM_NCDESTROY()
299 //}}AFX_MSG_MAP
300 ON_MESSAGE( WM_USER_DOMAIN_ADD, OnDomainAdd )
301 ON_MESSAGE( WM_USER_DOMAIN_REMOVE, OnDomainRemove )
302 ON_MESSAGE( WM_USER_SERVICE_ADD, OnServiceAdd )
303 ON_MESSAGE( WM_USER_SERVICE_REMOVE, OnServiceRemove )
304 ON_MESSAGE( WM_USER_RESOLVE, OnResolve )
305 END_MESSAGE_MAP()
306
307 #if 0
308 #pragma mark == Routines ==
309 #endif
310
311 //===========================================================================================================================
312 // ChooserDialog
313 //===========================================================================================================================
314
315 ChooserDialog::ChooserDialog( CWnd *inParent )
316 : CDialog( ChooserDialog::IDD, inParent)
317 {
318 //{{AFX_DATA_INIT(ChooserDialog)
319 // Note: the ClassWizard will add member initialization here
320 //}}AFX_DATA_INIT
321
322 // Load menu accelerator table.
323
324 mMenuAcceleratorTable = ::LoadAccelerators( AfxGetInstanceHandle(), MAKEINTRESOURCE( IDR_CHOOSER_DIALOG_MENU_ACCELERATORS ) );
325 assert( mMenuAcceleratorTable );
326
327 mBrowser = NULL;
328 mIsServiceBrowsing = false;
329 }
330
331 //===========================================================================================================================
332 // ~ChooserDialog
333 //===========================================================================================================================
334
335 ChooserDialog::~ChooserDialog( void )
336 {
337 if( mBrowser )
338 {
339 DNSStatus err;
340
341 err = DNSBrowserRelease( mBrowser, 0 );
342 assert( err == kDNSNoErr );
343 }
344 }
345
346 //===========================================================================================================================
347 // DoDataExchange
348 //===========================================================================================================================
349
350 void ChooserDialog::DoDataExchange( CDataExchange *pDX )
351 {
352 CDialog::DoDataExchange(pDX);
353
354 //{{AFX_DATA_MAP(ChooserDialog)
355 DDX_Control(pDX, IDC_SERVICE_LIST, mServiceList);
356 DDX_Control(pDX, IDC_DOMAIN_LIST, mDomainList);
357 DDX_Control(pDX, IDC_CHOOSER_LIST, mChooserList);
358 //}}AFX_DATA_MAP
359 }
360
361 //===========================================================================================================================
362 // OnInitDialog
363 //===========================================================================================================================
364
365 BOOL ChooserDialog::OnInitDialog( void )
366 {
367 HICON icon;
368 BOOL result;
369 CString tempString;
370 DNSStatus err;
371
372 // Initialize our parent.
373
374 CDialog::OnInitDialog();
375
376 // Set up the window icon.
377
378 icon = AfxGetApp()->LoadIcon( IDR_MAIN_ICON );
379 assert( icon );
380 if( icon )
381 {
382 SetIcon( icon, TRUE ); // Set big icon
383 SetIcon( icon, FALSE ); // Set small icon
384 }
385
386 // Set up the Domain List.
387
388 result = tempString.LoadString( IDS_CHOOSER_DOMAIN_COLUMN_NAME );
389 assert( result );
390 mDomainList.InsertColumn( 0, tempString, LVCFMT_LEFT, kDomainListDefaultDomainColumnWidth );
391
392 // Set up the Service List.
393
394 result = tempString.LoadString( IDS_CHOOSER_SERVICE_COLUMN_TYPE );
395 assert( result );
396 mServiceList.InsertColumn( 0, tempString, LVCFMT_LEFT, kServiceListDefaultServiceColumnTypeWidth );
397
398 result = tempString.LoadString( IDS_CHOOSER_SERVICE_COLUMN_DESC );
399 assert( result );
400 mServiceList.InsertColumn( 1, tempString, LVCFMT_LEFT, kServiceListDefaultServiceColumnDescWidth );
401
402 PopulateServicesList();
403
404 // Set up the Chooser List.
405
406 result = tempString.LoadString( IDS_CHOOSER_CHOOSER_NAME_COLUMN_NAME );
407 assert( result );
408 mChooserList.InsertColumn( 0, tempString, LVCFMT_LEFT, kChooserListDefaultNameColumnWidth );
409
410 result = tempString.LoadString( IDS_CHOOSER_CHOOSER_IP_COLUMN_NAME );
411 assert( result );
412 mChooserList.InsertColumn( 1, tempString, LVCFMT_LEFT, kChooserListDefaultIPColumnWidth );
413
414 // Set up the other controls.
415
416 UpdateInfoDisplay();
417
418 // Start browsing for domains.
419
420 err = DNSBrowserCreate( 0, BrowserCallBack, this, &mBrowser );
421 assert( err == kDNSNoErr );
422
423 err = DNSBrowserStartDomainSearch( mBrowser, 0 );
424 assert( err == kDNSNoErr );
425
426 return( true );
427 }
428
429 //===========================================================================================================================
430 // OnFileClose
431 //===========================================================================================================================
432
433 void ChooserDialog::OnFileClose()
434 {
435 OnClose();
436 }
437
438 //===========================================================================================================================
439 // OnActivate
440 //===========================================================================================================================
441
442 void ChooserDialog::OnActivate( UINT nState, CWnd* pWndOther, BOOL bMinimized )
443 {
444 // Always make the active window the "main" window so modal dialogs work better and the app quits after closing
445 // the last window.
446
447 gApp.m_pMainWnd = this;
448
449 CDialog::OnActivate(nState, pWndOther, bMinimized);
450 }
451
452 //===========================================================================================================================
453 // PostNcDestroy
454 //===========================================================================================================================
455
456 void ChooserDialog::PostNcDestroy()
457 {
458 // Call the base class to do the normal cleanup.
459
460 delete this;
461 }
462
463 //===========================================================================================================================
464 // PreTranslateMessage
465 //===========================================================================================================================
466
467 BOOL ChooserDialog::PreTranslateMessage(MSG* pMsg)
468 {
469 BOOL result;
470
471 result = false;
472 assert( mMenuAcceleratorTable );
473 if( mMenuAcceleratorTable )
474 {
475 result = ::TranslateAccelerator( m_hWnd, mMenuAcceleratorTable, pMsg );
476 }
477 if( !result )
478 {
479 result = CDialog::PreTranslateMessage( pMsg );
480 }
481 return( result );
482 }
483
484 //===========================================================================================================================
485 // OnInitMenuPopup
486 //===========================================================================================================================
487
488 void ChooserDialog::OnInitMenuPopup( CMenu *pPopupMenu, UINT nIndex, BOOL bSysMenu )
489 {
490 CDialog::OnInitMenuPopup( pPopupMenu, nIndex, bSysMenu );
491
492 switch( nIndex )
493 {
494 case kChooserMenuIndexFile:
495 break;
496
497 case kChooserMenuIndexHelp:
498 break;
499
500 default:
501 break;
502 }
503 }
504
505 //===========================================================================================================================
506 // OnExit
507 //===========================================================================================================================
508
509 void ChooserDialog::OnExit()
510 {
511 OnClose();
512 }
513
514 //===========================================================================================================================
515 // OnAbout
516 //===========================================================================================================================
517
518 void ChooserDialog::OnAbout()
519 {
520 AboutDialog dialog;
521
522 dialog.DoModal();
523 }
524
525 //===========================================================================================================================
526 // OnSysCommand
527 //===========================================================================================================================
528
529 void ChooserDialog::OnSysCommand( UINT inID, LPARAM inParam )
530 {
531 CDialog::OnSysCommand( inID, inParam );
532 }
533
534 //===========================================================================================================================
535 // OnClose
536 //===========================================================================================================================
537
538 void ChooserDialog::OnClose()
539 {
540 StopBrowsing();
541
542 gApp.m_pMainWnd = this;
543 DestroyWindow();
544 }
545
546 //===========================================================================================================================
547 // OnNcDestroy
548 //===========================================================================================================================
549
550 void ChooserDialog::OnNcDestroy()
551 {
552 gApp.m_pMainWnd = this;
553
554 CDialog::OnNcDestroy();
555 }
556
557 //===========================================================================================================================
558 // OnDomainListChanged
559 //===========================================================================================================================
560
561 void ChooserDialog::OnDomainListChanged( NMHDR *pNMHDR, LRESULT *pResult )
562 {
563 UNUSED_ALWAYS( pNMHDR );
564
565 // Domain list changes have similar effects to service list changes so reuse that code path by calling it here.
566
567 OnServiceListChanged( NULL, NULL );
568
569 *pResult = 0;
570 }
571
572 //===========================================================================================================================
573 // OnServiceListChanged
574 //===========================================================================================================================
575
576 void ChooserDialog::OnServiceListChanged( NMHDR *pNMHDR, LRESULT *pResult )
577 {
578 int selectedType;
579 int selectedDomain;
580
581 UNUSED_ALWAYS( pNMHDR );
582
583 // Stop any existing service search.
584
585 StopBrowsing();
586
587 // If a domain and service type are selected, start searching for the service type on the domain.
588
589 selectedType = mServiceList.GetNextItem( -1, LVNI_SELECTED );
590 selectedDomain = mDomainList.GetNextItem( -1, LVNI_SELECTED );
591
592 if( ( selectedType >= 0 ) && ( selectedDomain >= 0 ) )
593 {
594 CString s;
595 std::string utf8;
596 const char * type;
597
598 s = mDomainList.GetItemText( selectedDomain, 0 );
599 StringObjectToUTF8String( s, utf8 );
600 type = mServiceTypes[ selectedType ].serviceType.c_str();
601 if( *type != '\0' )
602 {
603 StartBrowsing( type, utf8.c_str() );
604 }
605 }
606
607 if( pResult )
608 {
609 *pResult = 0;
610 }
611 }
612
613 //===========================================================================================================================
614 // OnChooserListChanged
615 //===========================================================================================================================
616
617 void ChooserDialog::OnChooserListChanged( NMHDR *pNMHDR, LRESULT *pResult )
618 {
619 UNUSED_ALWAYS( pNMHDR );
620
621 UpdateInfoDisplay();
622 *pResult = 0;
623 }
624
625 //===========================================================================================================================
626 // OnChooserListDoubleClick
627 //===========================================================================================================================
628
629 void ChooserDialog::OnChooserListDoubleClick( NMHDR *pNMHDR, LRESULT *pResult )
630 {
631 int selectedItem;
632
633 UNUSED_ALWAYS( pNMHDR );
634
635 // Display the service instance if it is selected. Otherwise, clear all the info.
636
637 selectedItem = mChooserList.GetNextItem( -1, LVNI_SELECTED );
638 if( selectedItem >= 0 )
639 {
640 ServiceInstanceInfo * p;
641 CString url;
642 const KnownServiceEntry * service;
643
644 assert( selectedItem < (int) mServiceInstances.size() );
645 p = &mServiceInstances[ selectedItem ];
646
647 // Search for a known service type entry that matches.
648
649 for( service = kKnownServiceTable; service->serviceType; ++service )
650 {
651 if( p->type == service->serviceType )
652 {
653 break;
654 }
655 }
656 if( service->serviceType )
657 {
658 const char * text;
659
660 // Create a URL representing the service instance.
661
662 if( strcmp( service->serviceType, "_smb._tcp." ) == 0 )
663 {
664 // Special case for SMB (no port number).
665
666 url.Format( TEXT( "%s%s/" ), service->urlScheme, (const char *) p->ip.c_str() );
667 }
668 else if( strcmp( service->serviceType, "_ftp._tcp." ) == 0 )
669 {
670 // Special case for FTP to get login info.
671
672 LoginDialog dialog;
673 CString username;
674 CString password;
675
676 if( !dialog.GetLogin( username, password ) )
677 {
678 goto exit;
679 }
680
681 // Build URL in the following format:
682 //
683 // ftp://[username[:password]@]<ip>
684
685 url += service->urlScheme;
686 if( username.GetLength() > 0 )
687 {
688 url += username;
689 if( password.GetLength() > 0 )
690 {
691 url += ':';
692 url += password;
693 }
694 url += '@';
695 }
696 url += p->ip.c_str();
697 }
698 else if( strcmp( service->serviceType, "_http._tcp." ) == 0 )
699 {
700 // Special case for HTTP to exclude "path=" if present.
701
702 text = service->useText ? p->text.c_str() : "";
703 if( strncmp( text, "path=", 5 ) == 0 )
704 {
705 text += 5;
706 }
707 if( *text != '/' )
708 {
709 url.Format( TEXT( "%s%s/%s" ), service->urlScheme, (const char *) p->ip.c_str(), text );
710 }
711 else
712 {
713 url.Format( TEXT( "%s%s%s" ), service->urlScheme, (const char *) p->ip.c_str(), text );
714 }
715 }
716 else
717 {
718 text = service->useText ? p->text.c_str() : "";
719 url.Format( TEXT( "%s%s/%s" ), service->urlScheme, (const char *) p->ip.c_str(), text );
720 }
721
722 // Let the system open the URL in the correct app.
723
724 {
725 CWaitCursor waitCursor;
726
727 ShellExecute( NULL, TEXT( "open" ), url, TEXT( "" ), TEXT( "c:\\" ), SW_SHOWNORMAL );
728 }
729 }
730 }
731
732 exit:
733 *pResult = 0;
734 }
735
736 //===========================================================================================================================
737 // OnCancel
738 //===========================================================================================================================
739
740 void ChooserDialog::OnCancel()
741 {
742 // Do nothing.
743 }
744
745 //===========================================================================================================================
746 // PopulateServicesList
747 //===========================================================================================================================
748
749 void ChooserDialog::PopulateServicesList( void )
750 {
751 ServiceTypeVector::iterator i;
752 CString type;
753 CString desc;
754 std::string tmp;
755
756 // Add a fixed list of known services.
757
758 if( mServiceTypes.empty() )
759 {
760 const KnownServiceEntry * service;
761
762 for( service = kKnownServiceTable; service->serviceType; ++service )
763 {
764 ServiceTypeInfo info;
765
766 info.serviceType = service->serviceType;
767 info.description = service->description;
768 info.urlScheme = service->urlScheme;
769 mServiceTypes.push_back( info );
770 }
771 }
772
773 // Add each service to the list.
774
775 for( i = mServiceTypes.begin(); i != mServiceTypes.end(); ++i )
776 {
777 const char * p;
778 const char * q;
779
780 p = ( *i ).serviceType.c_str();
781 if( *p == '_' ) ++p; // Skip leading '_'.
782 q = strchr( p, '.' ); // Find first '.'.
783 if( q ) tmp.assign( p, (size_t)( q - p ) ); // Use only up to the first '.'.
784 else tmp.assign( p ); // No '.' so use the entire string.
785 UTF8StringToStringObject( tmp.c_str(), type );
786 UTF8StringToStringObject( ( *i ).description.c_str(), desc );
787
788 int n;
789
790 n = mServiceList.GetItemCount();
791 mServiceList.InsertItem( n, type );
792 mServiceList.SetItemText( n, 1, desc );
793 }
794
795 // Select the first service type by default.
796
797 if( !mServiceTypes.empty() )
798 {
799 mServiceList.SetItemState( 0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED );
800 }
801 }
802
803 //===========================================================================================================================
804 // UpdateInfoDisplay
805 //===========================================================================================================================
806
807 void ChooserDialog::UpdateInfoDisplay( void )
808 {
809 int selectedItem;
810 std::string name;
811 CString s;
812 std::string ip;
813 std::string ifIP;
814 std::string text;
815 std::string textNewLines;
816 std::string hostName;
817 CWnd * item;
818 std::string::iterator i;
819
820 // Display the service instance if it is selected. Otherwise, clear all the info.
821
822 selectedItem = mChooserList.GetNextItem( -1, LVNI_SELECTED );
823 if( selectedItem >= 0 )
824 {
825 ServiceInstanceInfo * p;
826
827 assert( selectedItem < (int) mServiceInstances.size() );
828 p = &mServiceInstances[ selectedItem ];
829
830 name = p->name;
831 ip = p->ip;
832 ifIP = p->ifIP;
833 text = p->text;
834 hostName = p->hostName;
835
836 // Sync up the list items with the actual data (IP address may change).
837
838 UTF8StringToStringObject( ip.c_str(), s );
839 mChooserList.SetItemText( selectedItem, 1, s );
840 }
841
842 // Name
843
844 item = (CWnd *) this->GetDlgItem( IDC_INFO_NAME_TEXT );
845 assert( item );
846 UTF8StringToStringObject( name.c_str(), s );
847 item->SetWindowText( s );
848
849 // IP
850
851 item = (CWnd *) this->GetDlgItem( IDC_INFO_IP_TEXT );
852 assert( item );
853 UTF8StringToStringObject( ip.c_str(), s );
854 item->SetWindowText( s );
855
856 // Interface
857
858 item = (CWnd *) this->GetDlgItem( IDC_INFO_INTERFACE_TEXT );
859 assert( item );
860 UTF8StringToStringObject( ifIP.c_str(), s );
861 item->SetWindowText( s );
862
863
864 item = (CWnd *) this->GetDlgItem( IDC_INFO_HOST_NAME_TEXT );
865 assert( item );
866 UTF8StringToStringObject( hostName.c_str(), s );
867 item->SetWindowText( s );
868
869 // Text
870
871 item = (CWnd *) this->GetDlgItem( IDC_INFO_TEXT_TEXT );
872 assert( item );
873 for( i = text.begin(); i != text.end(); ++i )
874 {
875 if( *i == '\1' )
876 {
877 textNewLines += "\r\n";
878 }
879 else
880 {
881 textNewLines += *i;
882 }
883 }
884 UTF8StringToStringObject( textNewLines.c_str(), s );
885 item->SetWindowText( s );
886 }
887
888 #if 0
889 #pragma mark -
890 #endif
891
892 //===========================================================================================================================
893 // OnDomainAdd
894 //===========================================================================================================================
895
896 LONG ChooserDialog::OnDomainAdd( WPARAM inWParam, LPARAM inLParam )
897 {
898 DomainEventInfo * p;
899 std::auto_ptr < DomainEventInfo > pAutoPtr;
900 int n;
901 int i;
902 CString domain;
903 CString s;
904 bool found;
905
906 UNUSED_ALWAYS( inWParam );
907
908 assert( inLParam );
909 p = reinterpret_cast <DomainEventInfo *> ( inLParam );
910 pAutoPtr.reset( p );
911
912 // Search to see if we already know about this domain. If not, add it to the list.
913
914 found = false;
915 domain = p->domain;
916 n = mDomainList.GetItemCount();
917 for( i = 0; i < n; ++i )
918 {
919 s = mDomainList.GetItemText( i, 0 );
920 if( s == domain )
921 {
922 found = true;
923 break;
924 }
925 }
926 if( !found )
927 {
928 int selectedItem;
929
930 mDomainList.InsertItem( n, domain );
931
932 // If no domains are selected and the domain being added is a default domain, select it.
933
934 selectedItem = mDomainList.GetNextItem( -1, LVNI_SELECTED );
935 if( ( selectedItem < 0 ) && ( p->eventType == kDNSBrowserEventTypeAddDefaultDomain ) )
936 {
937 mDomainList.SetItemState( n, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED );
938 }
939 }
940 return( 0 );
941 }
942
943 //===========================================================================================================================
944 // OnDomainRemove
945 //===========================================================================================================================
946
947 LONG ChooserDialog::OnDomainRemove( WPARAM inWParam, LPARAM inLParam )
948 {
949 DomainEventInfo * p;
950 std::auto_ptr < DomainEventInfo > pAutoPtr;
951 int n;
952 int i;
953 CString domain;
954 CString s;
955 bool found;
956
957 UNUSED_ALWAYS( inWParam );
958
959 assert( inLParam );
960 p = reinterpret_cast <DomainEventInfo *> ( inLParam );
961 pAutoPtr.reset( p );
962
963 // Search to see if we know about this domain. If so, remove it from the list.
964
965 found = false;
966 domain = p->domain;
967 n = mDomainList.GetItemCount();
968 for( i = 0; i < n; ++i )
969 {
970 s = mDomainList.GetItemText( i, 0 );
971 if( s == domain )
972 {
973 found = true;
974 break;
975 }
976 }
977 if( found )
978 {
979 mDomainList.DeleteItem( i );
980 }
981 return( 0 );
982 }
983
984 //===========================================================================================================================
985 // OnServiceAdd
986 //===========================================================================================================================
987
988 LONG ChooserDialog::OnServiceAdd( WPARAM inWParam, LPARAM inLParam )
989 {
990 ServiceEventInfo * p;
991 std::auto_ptr < ServiceEventInfo > pAutoPtr;
992
993 UNUSED_ALWAYS( inWParam );
994
995 assert( inLParam );
996 p = reinterpret_cast <ServiceEventInfo *> ( inLParam );
997 pAutoPtr.reset( p );
998
999 return( 0 );
1000 }
1001
1002 //===========================================================================================================================
1003 // OnServiceRemove
1004 //===========================================================================================================================
1005
1006 LONG ChooserDialog::OnServiceRemove( WPARAM inWParam, LPARAM inLParam )
1007 {
1008 ServiceEventInfo * p;
1009 std::auto_ptr < ServiceEventInfo > pAutoPtr;
1010 bool found;
1011 int n;
1012 int i;
1013
1014 UNUSED_ALWAYS( inWParam );
1015
1016 assert( inLParam );
1017 p = reinterpret_cast <ServiceEventInfo *> ( inLParam );
1018 pAutoPtr.reset( p );
1019
1020 // Search to see if we know about this service instance. If so, remove it from the list.
1021
1022 found = false;
1023 n = (int) mServiceInstances.size();
1024 for( i = 0; i < n; ++i )
1025 {
1026 ServiceInstanceInfo * q;
1027
1028 // If the name, type, domain, and interface match, treat it as the same service instance.
1029
1030 q = &mServiceInstances[ i ];
1031 if( ( p->name == q->name ) &&
1032 ( p->type == q->type ) &&
1033 ( p->domain == q->domain ) )
1034 {
1035 found = true;
1036 break;
1037 }
1038 }
1039 if( found )
1040 {
1041 mChooserList.DeleteItem( i );
1042 assert( i < (int) mServiceInstances.size() );
1043 mServiceInstances.erase( mServiceInstances.begin() + i );
1044 }
1045 return( 0 );
1046 }
1047
1048 //===========================================================================================================================
1049 // OnResolve
1050 //===========================================================================================================================
1051
1052 LONG ChooserDialog::OnResolve( WPARAM inWParam, LPARAM inLParam )
1053 {
1054 ServiceInstanceInfo * p;
1055 std::auto_ptr < ServiceInstanceInfo > pAutoPtr;
1056 int selectedType;
1057 int n;
1058 int i;
1059 bool found;
1060
1061 UNUSED_ALWAYS( inWParam );
1062
1063 assert( inLParam );
1064 p = reinterpret_cast <ServiceInstanceInfo *> ( inLParam );
1065 pAutoPtr.reset( p );
1066
1067 // Make sure it is for an item of the correct type. This handles any resolves that may have been queued up.
1068
1069 selectedType = mServiceList.GetNextItem( -1, LVNI_SELECTED );
1070 assert( selectedType >= 0 );
1071 if( selectedType >= 0 )
1072 {
1073 assert( selectedType <= (int) mServiceTypes.size() );
1074 if( p->type != mServiceTypes[ selectedType ].serviceType )
1075 {
1076 goto exit;
1077 }
1078 }
1079
1080 // Search to see if we know about this service instance. If so, update its info. Otherwise, add it to the list.
1081
1082 found = false;
1083 n = (int) mServiceInstances.size();
1084 for( i = 0; i < n; ++i )
1085 {
1086 ServiceInstanceInfo * q;
1087
1088 // If the name, type, domain, and interface matches, treat it as the same service instance.
1089
1090 q = &mServiceInstances[ i ];
1091 if( ( p->name == q->name ) &&
1092 ( p->type == q->type ) &&
1093 ( p->domain == q->domain ) &&
1094 ( p->ifIP == q->ifIP ) )
1095 {
1096 found = true;
1097 break;
1098 }
1099 }
1100 if( found )
1101 {
1102 mServiceInstances[ i ] = *p;
1103 }
1104 else
1105 {
1106 CString s;
1107
1108 mServiceInstances.push_back( *p );
1109 UTF8StringToStringObject( p->name.c_str(), s );
1110 mChooserList.InsertItem( n, s );
1111
1112 UTF8StringToStringObject( p->ip.c_str(), s );
1113 mChooserList.SetItemText( n, 1, s );
1114
1115 // If this is the only item, select it.
1116
1117 if( n == 0 )
1118 {
1119 mChooserList.SetItemState( n, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED );
1120 }
1121 }
1122 UpdateInfoDisplay();
1123
1124 exit:
1125 return( 0 );
1126 }
1127
1128 //===========================================================================================================================
1129 // StartBrowsing
1130 //===========================================================================================================================
1131
1132 void ChooserDialog::StartBrowsing( const char *inType, const char *inDomain )
1133 {
1134 DNSStatus err;
1135
1136 assert( mServiceInstances.empty() );
1137 assert( mChooserList.GetItemCount() == 0 );
1138 assert( !mIsServiceBrowsing );
1139
1140 mChooserList.DeleteAllItems();
1141 mServiceInstances.clear();
1142
1143 mIsServiceBrowsing = true;
1144 err = DNSBrowserStartServiceSearch( mBrowser, kDNSBrowserFlagAutoResolve, inType, inDomain );
1145 assert( err == kDNSNoErr );
1146 }
1147
1148 //===========================================================================================================================
1149 // StopBrowsing
1150 //===========================================================================================================================
1151
1152 void ChooserDialog::StopBrowsing( void )
1153 {
1154 // If searching, stop.
1155
1156 if( mIsServiceBrowsing )
1157 {
1158 DNSStatus err;
1159
1160 mIsServiceBrowsing = false;
1161 err = DNSBrowserStopServiceSearch( mBrowser, 0 );
1162 assert( err == kDNSNoErr );
1163 }
1164
1165 // Remove all service instances.
1166
1167 mChooserList.DeleteAllItems();
1168 assert( mChooserList.GetItemCount() == 0 );
1169 mServiceInstances.clear();
1170 assert( mServiceInstances.empty() );
1171 UpdateInfoDisplay();
1172 }
1173
1174 #if 0
1175 #pragma mark -
1176 #endif
1177
1178 //===========================================================================================================================
1179 // BrowserCallBack
1180 //===========================================================================================================================
1181
1182 static void
1183 BrowserCallBack(
1184 void * inContext,
1185 DNSBrowserRef inRef,
1186 DNSStatus inStatusCode,
1187 const DNSBrowserEvent * inEvent )
1188 {
1189 ChooserDialog * dialog;
1190 UINT message;
1191 BOOL posted;
1192
1193 UNUSED_ALWAYS( inStatusCode );
1194 UNUSED_ALWAYS( inRef );
1195
1196 // Check parameters.
1197
1198 assert( inContext );
1199 dialog = reinterpret_cast <ChooserDialog *> ( inContext );
1200
1201 try
1202 {
1203 switch( inEvent->type )
1204 {
1205 case kDNSBrowserEventTypeRelease:
1206 break;
1207
1208 // Domains
1209
1210 case kDNSBrowserEventTypeAddDomain:
1211 case kDNSBrowserEventTypeAddDefaultDomain:
1212 case kDNSBrowserEventTypeRemoveDomain:
1213 {
1214 DomainEventInfo * domain;
1215 std::auto_ptr < DomainEventInfo > domainAutoPtr;
1216
1217 domain = new DomainEventInfo;
1218 domainAutoPtr.reset( domain );
1219
1220 domain->eventType = inEvent->type;
1221 domain->domain = inEvent->data.addDomain.domain;
1222 domain->ifIP = inEvent->data.addDomain.interfaceIP;
1223
1224 message = ( inEvent->type == kDNSBrowserEventTypeRemoveDomain ) ? WM_USER_DOMAIN_REMOVE : WM_USER_DOMAIN_ADD;
1225 posted = ::PostMessage( dialog->GetSafeHwnd(), message, 0, (LPARAM) domain );
1226 assert( posted );
1227 if( posted )
1228 {
1229 domainAutoPtr.release();
1230 }
1231 break;
1232 }
1233
1234 // Services
1235
1236 case kDNSBrowserEventTypeAddService:
1237 case kDNSBrowserEventTypeRemoveService:
1238 {
1239 ServiceEventInfo * service;
1240 std::auto_ptr < ServiceEventInfo > serviceAutoPtr;
1241
1242 service = new ServiceEventInfo;
1243 serviceAutoPtr.reset( service );
1244
1245 service->eventType = inEvent->type;
1246 service->name = inEvent->data.addService.name;
1247 service->type = inEvent->data.addService.type;
1248 service->domain = inEvent->data.addService.domain;
1249 service->ifIP = inEvent->data.addService.interfaceIP;
1250
1251 message = ( inEvent->type == kDNSBrowserEventTypeAddService ) ? WM_USER_SERVICE_ADD : WM_USER_SERVICE_REMOVE;
1252 posted = ::PostMessage( dialog->GetSafeHwnd(), message, 0, (LPARAM) service );
1253 assert( posted );
1254 if( posted )
1255 {
1256 serviceAutoPtr.release();
1257 }
1258 break;
1259 }
1260
1261 // Resolves
1262
1263 case kDNSBrowserEventTypeResolved:
1264 if( inEvent->data.resolved->address.addressType == kDNSNetworkAddressTypeIPv4 )
1265 {
1266 ServiceInstanceInfo * serviceInstance;
1267 std::auto_ptr < ServiceInstanceInfo > serviceInstanceAutoPtr;
1268 char s[ 32 ];
1269
1270 serviceInstance = new ServiceInstanceInfo;
1271 serviceInstanceAutoPtr.reset( serviceInstance );
1272
1273 serviceInstance->name = inEvent->data.resolved->name;
1274 serviceInstance->type = inEvent->data.resolved->type;
1275 serviceInstance->domain = inEvent->data.resolved->domain;
1276 serviceInstance->ip = DNSNetworkAddressToString( &inEvent->data.resolved->address, s );
1277 serviceInstance->ifIP = DNSNetworkAddressToString( &inEvent->data.resolved->interfaceIP, s );
1278 serviceInstance->text = inEvent->data.resolved->textRecord;
1279 serviceInstance->hostName = inEvent->data.resolved->hostName;
1280
1281 posted = ::PostMessage( dialog->GetSafeHwnd(), WM_USER_RESOLVE, 0, (LPARAM) serviceInstance );
1282 assert( posted );
1283 if( posted )
1284 {
1285 serviceInstanceAutoPtr.release();
1286 }
1287 }
1288 break;
1289
1290 default:
1291 break;
1292 }
1293 }
1294 catch( ... )
1295 {
1296 // Don't let exceptions escape.
1297 }
1298 }
1299
1300 //===========================================================================================================================
1301 // DNSNetworkAddressToString
1302 //
1303 // Note: Currently only supports IPv4 network addresses.
1304 //===========================================================================================================================
1305
1306 static char * DNSNetworkAddressToString( const DNSNetworkAddress *inAddr, char *outString )
1307 {
1308 const DNSUInt8 * p;
1309 DNSUInt16 port;
1310
1311 p = inAddr->u.ipv4.addr.v8;
1312 port = ntohs( inAddr->u.ipv4.port.v16 );
1313 if( port != kDNSPortInvalid )
1314 {
1315 sprintf( outString, "%u.%u.%u.%u:%u", p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ], port );
1316 }
1317 else
1318 {
1319 sprintf( outString, "%u.%u.%u.%u", p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ] );
1320 }
1321 return( outString );
1322 }
1323
1324 //===========================================================================================================================
1325 // UTF8StringToStringObject
1326 //===========================================================================================================================
1327
1328 static DWORD UTF8StringToStringObject( const char *inUTF8, CString &inObject )
1329 {
1330 DWORD err;
1331 int n;
1332 BSTR unicode;
1333
1334 unicode = NULL;
1335
1336 n = MultiByteToWideChar( CP_UTF8, 0, inUTF8, -1, NULL, 0 );
1337 if( n > 0 )
1338 {
1339 unicode = (BSTR) malloc( (size_t)( n * sizeof( wchar_t ) ) );
1340 if( !unicode )
1341 {
1342 err = ERROR_INSUFFICIENT_BUFFER;
1343 goto exit;
1344 }
1345
1346 n = MultiByteToWideChar( CP_UTF8, 0, inUTF8, -1, unicode, n );
1347 try
1348 {
1349 inObject = unicode;
1350 }
1351 catch( ... )
1352 {
1353 err = ERROR_NO_UNICODE_TRANSLATION;
1354 goto exit;
1355 }
1356 }
1357 else
1358 {
1359 inObject = "";
1360 }
1361 err = 0;
1362
1363 exit:
1364 if( unicode )
1365 {
1366 free( unicode );
1367 }
1368 return( err );
1369 }
1370
1371 //===========================================================================================================================
1372 // StringObjectToUTF8String
1373 //===========================================================================================================================
1374
1375 static DWORD StringObjectToUTF8String( CString &inObject, std::string &outUTF8 )
1376 {
1377 DWORD err;
1378 BSTR unicode;
1379 int nUnicode;
1380 int n;
1381 char * utf8;
1382
1383 unicode = NULL;
1384 utf8 = NULL;
1385
1386 nUnicode = inObject.GetLength();
1387 if( nUnicode > 0 )
1388 {
1389 unicode = inObject.AllocSysString();
1390 n = WideCharToMultiByte( CP_UTF8, 0, unicode, nUnicode, NULL, 0, NULL, NULL );
1391 assert( n > 0 );
1392
1393 utf8 = (char *) malloc( (size_t) n );
1394 assert( utf8 );
1395 if( !utf8 ) { err = ERROR_INSUFFICIENT_BUFFER; goto exit; }
1396
1397 n = WideCharToMultiByte( CP_UTF8, 0, unicode, nUnicode, utf8, n, NULL, NULL );
1398 assert( n > 0 );
1399
1400 try
1401 {
1402 outUTF8.assign( utf8, n );
1403 }
1404 catch( ... )
1405 {
1406 err = ERROR_NO_UNICODE_TRANSLATION;
1407 goto exit;
1408 }
1409 }
1410 else
1411 {
1412 outUTF8.clear();
1413 }
1414 err = 0;
1415
1416 exit:
1417 if( unicode )
1418 {
1419 SysFreeString( unicode );
1420 }
1421 if( utf8 )
1422 {
1423 free( utf8 );
1424 }
1425 return( err );
1426 }