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
41static 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
54enum
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
90struct	KnownServiceEntry
91{
92	const char *		serviceType;
93	const char *		description;
94	const char *		urlScheme;
95	bool				useText;
96};
97
98static 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
241struct	DomainEventInfo
242{
243	DNSBrowserEventType		eventType;
244	CString					domain;
245	DNSNetworkAddress		ifIP;
246};
247
248struct	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
265static void
266	BrowserCallBack(
267		void *					inContext,
268		DNSBrowserRef			inRef,
269		DNSStatus				inStatusCode,
270		const DNSBrowserEvent *	inEvent );
271
272static char *	DNSNetworkAddressToString( const DNSNetworkAddress *inAddr, char *outString );
273
274static DWORD	UTF8StringToStringObject( const char *inUTF8, CString &inObject );
275static 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
285BEGIN_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 )
305END_MESSAGE_MAP()
306
307#if 0
308#pragma mark == Routines ==
309#endif
310
311//===========================================================================================================================
312//	ChooserDialog
313//===========================================================================================================================
314
315ChooserDialog::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
335ChooserDialog::~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
350void 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
365BOOL	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
433void ChooserDialog::OnFileClose()
434{
435	OnClose();
436}
437
438//===========================================================================================================================
439//	OnActivate
440//===========================================================================================================================
441
442void 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
456void	ChooserDialog::PostNcDestroy()
457{
458	// Call the base class to do the normal cleanup.
459
460	delete this;
461}
462
463//===========================================================================================================================
464//	PreTranslateMessage
465//===========================================================================================================================
466
467BOOL	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
488void	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
509void ChooserDialog::OnExit()
510{
511	OnClose();
512}
513
514//===========================================================================================================================
515//	OnAbout
516//===========================================================================================================================
517
518void	ChooserDialog::OnAbout()
519{
520	AboutDialog		dialog;
521
522	dialog.DoModal();
523}
524
525//===========================================================================================================================
526//	OnSysCommand
527//===========================================================================================================================
528
529void	ChooserDialog::OnSysCommand( UINT inID, LPARAM inParam )
530{
531	CDialog::OnSysCommand( inID, inParam );
532}
533
534//===========================================================================================================================
535//	OnClose
536//===========================================================================================================================
537
538void ChooserDialog::OnClose()
539{
540	StopBrowsing();
541
542	gApp.m_pMainWnd = this;
543	DestroyWindow();
544}
545
546//===========================================================================================================================
547//	OnNcDestroy
548//===========================================================================================================================
549
550void ChooserDialog::OnNcDestroy()
551{
552	gApp.m_pMainWnd = this;
553
554	CDialog::OnNcDestroy();
555}
556
557//===========================================================================================================================
558//	OnDomainListChanged
559//===========================================================================================================================
560
561void	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
576void	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
617void	ChooserDialog::OnChooserListChanged( NMHDR *pNMHDR, LRESULT *pResult )
618{
619	UNUSED_ALWAYS( pNMHDR );
620
621	UpdateInfoDisplay();
622	*pResult = 0;
623}
624
625//===========================================================================================================================
626//	OnChooserListDoubleClick
627//===========================================================================================================================
628
629void	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
732exit:
733	*pResult = 0;
734}
735
736//===========================================================================================================================
737//	OnCancel
738//===========================================================================================================================
739
740void ChooserDialog::OnCancel()
741{
742	// Do nothing.
743}
744
745//===========================================================================================================================
746//	PopulateServicesList
747//===========================================================================================================================
748
749void	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
807void	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
896LONG	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
947LONG	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
988LONG	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
1006LONG	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
1052LONG	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
1124exit:
1125	return( 0 );
1126}
1127
1128//===========================================================================================================================
1129//	StartBrowsing
1130//===========================================================================================================================
1131
1132void	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
1152void	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
1182static 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
1306static 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
1328static 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
1363exit:
1364	if( unicode )
1365	{
1366		free( unicode );
1367	}
1368	return( err );
1369}
1370
1371//===========================================================================================================================
1372//	StringObjectToUTF8String
1373//===========================================================================================================================
1374
1375static 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
1416exit:
1417	if( unicode )
1418	{
1419		SysFreeString( unicode );
1420	}
1421	if( utf8 )
1422	{
1423		free( utf8 );
1424	}
1425	return( err );
1426}
1427