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