| /* -*- Mode: C; tab-width: 4 -*- |
| * |
| * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "stdafx.h" |
| |
| #include "DNSServices.h" |
| |
| #include "Application.h" |
| #include "AboutDialog.h" |
| #include "LoginDialog.h" |
| #include "Resource.h" |
| |
| #include "ChooserDialog.h" |
| |
| #ifdef _DEBUG |
| #define new DEBUG_NEW |
| #undef THIS_FILE |
| static char THIS_FILE[] = __FILE__; |
| #endif |
| |
| #if 0 |
| #pragma mark == Constants == |
| #endif |
| |
| //=========================================================================================================================== |
| // Constants |
| //=========================================================================================================================== |
| |
| // Menus |
| |
| enum |
| { |
| kChooserMenuIndexFile = 0, |
| kChooserMenuIndexHelp = 1 |
| }; |
| |
| // Domain List |
| |
| #define kDomainListDefaultDomainColumnWidth 164 |
| |
| // Service List |
| |
| #define kServiceListDefaultServiceColumnTypeWidth 146 |
| #define kServiceListDefaultServiceColumnDescWidth 230 |
| |
| // Chooser List |
| |
| #define kChooserListDefaultNameColumnWidth 190 |
| #define kChooserListDefaultIPColumnWidth 120 |
| |
| // Windows User Messages |
| |
| #define WM_USER_DOMAIN_ADD ( WM_USER + 0x100 ) |
| #define WM_USER_DOMAIN_REMOVE ( WM_USER + 0x101 ) |
| #define WM_USER_SERVICE_ADD ( WM_USER + 0x102 ) |
| #define WM_USER_SERVICE_REMOVE ( WM_USER + 0x103 ) |
| #define WM_USER_RESOLVE ( WM_USER + 0x104 ) |
| |
| #if 0 |
| #pragma mark == Constants - Service Table == |
| #endif |
| |
| //=========================================================================================================================== |
| // Constants - Service Table |
| //=========================================================================================================================== |
| |
| struct KnownServiceEntry |
| { |
| const char * serviceType; |
| const char * description; |
| const char * urlScheme; |
| bool useText; |
| }; |
| |
| static const KnownServiceEntry kKnownServiceTable[] = |
| { |
| { "_accountedge._tcp.", "MYOB AccountEdge", "", false }, |
| { "_aecoretech._tcp.", "Apple Application Engineering Services", "", false }, |
| { "_afpovertcp._tcp.", "Apple File Sharing (AFP)", "afp://", false }, |
| { "_airport._tcp.", "AirPort Base Station", "", false }, |
| { "_apple-sasl._tcp.", "Apple Password Server", "", false }, |
| { "_aquamon._tcp.", "AquaMon", "", false }, |
| { "_async._tcp", "address-o-sync", "", false }, |
| { "_auth._tcp.", "Authentication Service", "", false }, |
| { "_bootps._tcp.", "Bootstrap Protocol Server", "", false }, |
| { "_bousg._tcp.", "Bag Of Unusual Strategy Games", "", false }, |
| { "_browse._udp.", "DNS Service Discovery", "", false }, |
| { "_cheat._tcp.", "The Cheat", "", false }, |
| { "_chess._tcp", "Project Gridlock", "", false }, |
| { "_chfts._tcp", "Fluid Theme Server", "", false }, |
| { "_clipboard._tcp", "Clipboard Sharing", "", false }, |
| { "_contactserver._tcp.", "Now Up-to-Date & Contact", "", false }, |
| { "_cvspserver._tcp", "CVS PServer", "", false }, |
| { "_cytv._tcp.", "CyTV Network streaming for Elgato EyeTV", "", false }, |
| { "_daap._tcp.", "Digital Audio Access Protocol (iTunes)", "daap://", false }, |
| { "_distcc._tcp", "Distributed Compiler", "", false }, |
| { "_dns-sd._udp", "DNS Service Discovery", "", false }, |
| { "_dpap._tcp.", "Digital Picture Access Protocol (iPhoto)", "", false }, |
| { "_earphoria._tcp.", "Earphoria", "", false }, |
| { "_ecbyesfsgksc._tcp.", "Net Monitor Anti-Piracy Service", "", false }, |
| { "_eheap._tcp.", "Interactive Room Software", "", false }, |
| { "_embrace._tcp.", "DataEnvoy", "", false }, |
| { "_eppc._tcp.", "Remote AppleEvents", "eppc://", false }, |
| { "_exec._tcp.", "Remote Process Execution", "", false }, |
| { "_facespan._tcp.", "FaceSpan", "", false }, |
| { "_fjork._tcp.", "Fjork", "", false }, |
| { "_ftp._tcp.", "File Transfer (FTP)", "ftp://", false }, |
| { "_ftpcroco._tcp.", "Crocodile FTP Server", "", false }, |
| { "_gbs-smp._tcp.", "SnapMail", "", false }, |
| { "_gbs-stp._tcp.", "SnapTalk", "", false }, |
| { "_grillezvous._tcp.", "Roxio ToastAnywhere(tm) Recorder Sharing", "", false }, |
| { "_h323._tcp.", "H.323", "", false }, |
| { "_hotwayd._tcp", "Hotwayd", "", false }, |
| { "_http._tcp.", "Web Server (HTTP)", "http://", true }, |
| { "_hydra._tcp", "SubEthaEdit", "", false }, |
| { "_ica-networking._tcp.", "Image Capture Networking", "", false }, |
| { "_ichalkboard._tcp.", "iChalk", "", false }, |
| { "_ichat._tcp.", "iChat", "ichat://", false }, |
| { "_iconquer._tcp.", "iConquer", "", false }, |
| { "_imap._tcp.", "Internet Message Access Protocol", "", false }, |
| { "_imidi._tcp.", "iMidi", "", false }, |
| { "_ipp._tcp.", "Printer (IPP)", "ipp://", false }, |
| { "_ishare._tcp.", "iShare", "", false }, |
| { "_isparx._tcp.", "iSparx", "", false }, |
| { "_istorm._tcp", "iStorm", "", false }, |
| { "_iwork._tcp.", "iWork Server", "", false }, |
| { "_liaison._tcp.", "Liaison", "", false }, |
| { "_login._tcp.", "Remote Login a la Telnet", "", false }, |
| { "_lontalk._tcp.", "LonTalk over IP (ANSI 852)", "", false }, |
| { "_lonworks._tcp.", "Echelon LNS Remote Client", "", false }, |
| { "_macfoh-remote._tcp.", "MacFOH Remote", "", false }, |
| { "_moneyworks._tcp.", "MoneyWorks", "", false }, |
| { "_mp3sushi._tcp", "MP3 Sushi", "", false }, |
| { "_mttp._tcp.", "MenuTunes Sharing", "", false }, |
| { "_ncbroadcast._tcp.", "Network Clipboard Broadcasts", "", false }, |
| { "_ncdirect._tcp.", "Network Clipboard Direct Transfers", "", false }, |
| { "_ncsyncserver._tcp.", "Network Clipboard Sync Server", "", false }, |
| { "_newton-dock._tcp.", "Escale", "", false }, |
| { "_nfs._tcp", "NFS", "", false }, |
| { "_nssocketport._tcp.", "DO over NSSocketPort", "", false }, |
| { "_omni-bookmark._tcp.", "OmniWeb", "", false }, |
| { "_openbase._tcp.", "OpenBase SQL", "", false }, |
| { "_p2pchat._tcp.", "Peer-to-Peer Chat", "", false }, |
| { "_pdl-datastream._tcp.", "Printer (PDL)", "pdl://", false }, |
| { "_poch._tcp.", "Parallel OperatiOn and Control Heuristic", "", false }, |
| { "_pop_2_ambrosia._tcp.", "Pop-Pop", "", false }, |
| { "_pop3._tcp", "POP3 Server", "", false }, |
| { "_postgresql._tcp", "PostgreSQL Server", "", false }, |
| { "_presence._tcp", "iChat AV", "", false }, |
| { "_printer._tcp.", "Printer (LPR)", "lpr://", false }, |
| { "_ptp._tcp.", "Picture Transfer (PTP)", "ptp://", false }, |
| { "_register._tcp", "DNS Service Discovery", "", false }, |
| { "_rfb._tcp.", "Remote Frame Buffer", "", false }, |
| { "_riousbprint._tcp.", "Remote I/O USB Printer Protocol", "", false }, |
| { "_rtsp._tcp.", "Real Time Stream Control Protocol", "", false }, |
| { "_safarimenu._tcp", "Safari Menu", "", false }, |
| { "_scone._tcp", "Scone", "", false }, |
| { "_sdsharing._tcp.", "Speed Download", "", false }, |
| { "_seeCard._tcp.", "seeCard", "", false }, |
| { "_services._udp.", "DNS Service Discovery", "", false }, |
| { "_shell._tcp.", "like exec, but automatic authentication", "", false }, |
| { "_shout._tcp.", "Shout", "", false }, |
| { "_shoutcast._tcp", "Nicecast", "", false }, |
| { "_smb._tcp.", "Windows File Sharing (SMB)", "smb://", false }, |
| { "_soap._tcp.", "Simple Object Access Protocol", "", false }, |
| { "_spincrisis._tcp.", "Spin Crisis", "", false }, |
| { "_spl-itunes._tcp.", "launchTunes", "", false }, |
| { "_spr-itunes._tcp.", "netTunes", "", false }, |
| { "_ssh._tcp.", "Secure Shell (SSH)", "ssh://", false }, |
| { "_ssscreenshare._tcp", "Screen Sharing", "", false }, |
| { "_sge-exec._tcp", "Sun Grid Engine (Execution Host)", "", false }, |
| { "_sge-qmaster._tcp", "Sun Grid Engine (Master)", "", false }, |
| { "_stickynotes._tcp", "Sticky Notes", "", false }, |
| { "_strateges._tcp", "Strateges", "", false }, |
| { "_sxqdea._tcp", "Synchronize! Pro X", "", false }, |
| { "_sybase-tds._tcp", "Sybase Server", "", false }, |
| { "_tce._tcp", "Power Card", "", false }, |
| { "_teamlist._tcp", "ARTIS Team Task", "", false }, |
| { "_teleport._tcp", "teleport", "", false }, |
| { "_telnet._tcp.", "Telnet", "telnet://", false }, |
| { "_tftp._tcp.", "Trivial File Transfer (TFTP)", "tftp://", false }, |
| { "_tinavigator._tcp.", "TI Navigator", "", false }, |
| { "_tivo_servemedia._tcp", "TiVo", "", false }, |
| { "_upnp._tcp.", "Universal Plug and Play", "", false }, |
| { "_utest._tcp.", "uTest", "", false }, |
| { "_vue4rendercow._tcp", "VueProRenderCow", "", false }, |
| { "_webdav._tcp.", "WebDAV", "webdav://", false }, |
| { "_whamb._tcp.", "Whamb", "", false }, |
| { "_workstation._tcp", "Macintosh Manager", "", false }, |
| { "_ws._tcp", "Web Services", "", false }, |
| { "_xserveraid._tcp.", "Xserve RAID", "xsr://", false }, |
| { "_xsync._tcp.", "Xserve RAID Synchronization", "", false }, |
| |
| { "", "", "", false }, |
| |
| // Unofficial and invalid service types that will be phased out: |
| |
| { "_clipboardsharing._tcp.", "ClipboardSharing", "", false }, |
| { "_MacOSXDupSuppress._tcp.", "Mac OS X Duplicate Suppression", "", false }, |
| { "_netmonitorserver._tcp.", "Net Monitor Server", "", false }, |
| { "_networkclipboard._tcp.", "Network Clipboard", "", false }, |
| { "_slimdevices_slimp3_cli._tcp.", "SliMP3 Server Command-Line Interface", "", false }, |
| { "_slimdevices_slimp3_http._tcp.", "SliMP3 Server Web Interface", "", false }, |
| { "_tieducationalhandhelddevice._tcp.", "TI Connect Manager", "", false }, |
| { "_tivo_servemedia._tcp.", "TiVo", "", false }, |
| |
| { NULL, NULL, NULL, false }, |
| }; |
| |
| #if 0 |
| #pragma mark == Structures == |
| #endif |
| |
| //=========================================================================================================================== |
| // Structures |
| //=========================================================================================================================== |
| |
| struct DomainEventInfo |
| { |
| DNSBrowserEventType eventType; |
| CString domain; |
| DNSNetworkAddress ifIP; |
| }; |
| |
| struct ServiceEventInfo |
| { |
| DNSBrowserEventType eventType; |
| std::string name; |
| std::string type; |
| std::string domain; |
| DNSNetworkAddress ifIP; |
| }; |
| |
| #if 0 |
| #pragma mark == Prototypes == |
| #endif |
| |
| //=========================================================================================================================== |
| // Prototypes |
| //=========================================================================================================================== |
| |
| static void |
| BrowserCallBack( |
| void * inContext, |
| DNSBrowserRef inRef, |
| DNSStatus inStatusCode, |
| const DNSBrowserEvent * inEvent ); |
| |
| static char * DNSNetworkAddressToString( const DNSNetworkAddress *inAddr, char *outString ); |
| |
| static DWORD UTF8StringToStringObject( const char *inUTF8, CString &inObject ); |
| static DWORD StringObjectToUTF8String( CString &inObject, std::string &outUTF8 ); |
| |
| #if 0 |
| #pragma mark == Message Map == |
| #endif |
| |
| //=========================================================================================================================== |
| // Message Map |
| //=========================================================================================================================== |
| |
| BEGIN_MESSAGE_MAP(ChooserDialog, CDialog) |
| //{{AFX_MSG_MAP(ChooserDialog) |
| ON_WM_SYSCOMMAND() |
| ON_NOTIFY(LVN_ITEMCHANGED, IDC_DOMAIN_LIST, OnDomainListChanged) |
| ON_NOTIFY(LVN_ITEMCHANGED, IDC_SERVICE_LIST, OnServiceListChanged) |
| ON_NOTIFY(LVN_ITEMCHANGED, IDC_CHOOSER_LIST, OnChooserListChanged) |
| ON_NOTIFY(NM_DBLCLK, IDC_CHOOSER_LIST, OnChooserListDoubleClick) |
| ON_COMMAND(ID_HELP_ABOUT, OnAbout) |
| ON_WM_INITMENUPOPUP() |
| ON_WM_ACTIVATE() |
| ON_COMMAND(ID_FILE_CLOSE, OnFileClose) |
| ON_COMMAND(ID_FILE_EXIT, OnExit) |
| ON_WM_CLOSE() |
| ON_WM_NCDESTROY() |
| //}}AFX_MSG_MAP |
| ON_MESSAGE( WM_USER_DOMAIN_ADD, OnDomainAdd ) |
| ON_MESSAGE( WM_USER_DOMAIN_REMOVE, OnDomainRemove ) |
| ON_MESSAGE( WM_USER_SERVICE_ADD, OnServiceAdd ) |
| ON_MESSAGE( WM_USER_SERVICE_REMOVE, OnServiceRemove ) |
| ON_MESSAGE( WM_USER_RESOLVE, OnResolve ) |
| END_MESSAGE_MAP() |
| |
| #if 0 |
| #pragma mark == Routines == |
| #endif |
| |
| //=========================================================================================================================== |
| // ChooserDialog |
| //=========================================================================================================================== |
| |
| ChooserDialog::ChooserDialog( CWnd *inParent ) |
| : CDialog( ChooserDialog::IDD, inParent) |
| { |
| //{{AFX_DATA_INIT(ChooserDialog) |
| // Note: the ClassWizard will add member initialization here |
| //}}AFX_DATA_INIT |
| |
| // Load menu accelerator table. |
| |
| mMenuAcceleratorTable = ::LoadAccelerators( AfxGetInstanceHandle(), MAKEINTRESOURCE( IDR_CHOOSER_DIALOG_MENU_ACCELERATORS ) ); |
| assert( mMenuAcceleratorTable ); |
| |
| mBrowser = NULL; |
| mIsServiceBrowsing = false; |
| } |
| |
| //=========================================================================================================================== |
| // ~ChooserDialog |
| //=========================================================================================================================== |
| |
| ChooserDialog::~ChooserDialog( void ) |
| { |
| if( mBrowser ) |
| { |
| DNSStatus err; |
| |
| err = DNSBrowserRelease( mBrowser, 0 ); |
| assert( err == kDNSNoErr ); |
| } |
| } |
| |
| //=========================================================================================================================== |
| // DoDataExchange |
| //=========================================================================================================================== |
| |
| void ChooserDialog::DoDataExchange( CDataExchange *pDX ) |
| { |
| CDialog::DoDataExchange(pDX); |
| |
| //{{AFX_DATA_MAP(ChooserDialog) |
| DDX_Control(pDX, IDC_SERVICE_LIST, mServiceList); |
| DDX_Control(pDX, IDC_DOMAIN_LIST, mDomainList); |
| DDX_Control(pDX, IDC_CHOOSER_LIST, mChooserList); |
| //}}AFX_DATA_MAP |
| } |
| |
| //=========================================================================================================================== |
| // OnInitDialog |
| //=========================================================================================================================== |
| |
| BOOL ChooserDialog::OnInitDialog( void ) |
| { |
| HICON icon; |
| BOOL result; |
| CString tempString; |
| DNSStatus err; |
| |
| // Initialize our parent. |
| |
| CDialog::OnInitDialog(); |
| |
| // Set up the window icon. |
| |
| icon = AfxGetApp()->LoadIcon( IDR_MAIN_ICON ); |
| assert( icon ); |
| if( icon ) |
| { |
| SetIcon( icon, TRUE ); // Set big icon |
| SetIcon( icon, FALSE ); // Set small icon |
| } |
| |
| // Set up the Domain List. |
| |
| result = tempString.LoadString( IDS_CHOOSER_DOMAIN_COLUMN_NAME ); |
| assert( result ); |
| mDomainList.InsertColumn( 0, tempString, LVCFMT_LEFT, kDomainListDefaultDomainColumnWidth ); |
| |
| // Set up the Service List. |
| |
| result = tempString.LoadString( IDS_CHOOSER_SERVICE_COLUMN_TYPE ); |
| assert( result ); |
| mServiceList.InsertColumn( 0, tempString, LVCFMT_LEFT, kServiceListDefaultServiceColumnTypeWidth ); |
| |
| result = tempString.LoadString( IDS_CHOOSER_SERVICE_COLUMN_DESC ); |
| assert( result ); |
| mServiceList.InsertColumn( 1, tempString, LVCFMT_LEFT, kServiceListDefaultServiceColumnDescWidth ); |
| |
| PopulateServicesList(); |
| |
| // Set up the Chooser List. |
| |
| result = tempString.LoadString( IDS_CHOOSER_CHOOSER_NAME_COLUMN_NAME ); |
| assert( result ); |
| mChooserList.InsertColumn( 0, tempString, LVCFMT_LEFT, kChooserListDefaultNameColumnWidth ); |
| |
| result = tempString.LoadString( IDS_CHOOSER_CHOOSER_IP_COLUMN_NAME ); |
| assert( result ); |
| mChooserList.InsertColumn( 1, tempString, LVCFMT_LEFT, kChooserListDefaultIPColumnWidth ); |
| |
| // Set up the other controls. |
| |
| UpdateInfoDisplay(); |
| |
| // Start browsing for domains. |
| |
| err = DNSBrowserCreate( 0, BrowserCallBack, this, &mBrowser ); |
| assert( err == kDNSNoErr ); |
| |
| err = DNSBrowserStartDomainSearch( mBrowser, 0 ); |
| assert( err == kDNSNoErr ); |
| |
| return( true ); |
| } |
| |
| //=========================================================================================================================== |
| // OnFileClose |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnFileClose() |
| { |
| OnClose(); |
| } |
| |
| //=========================================================================================================================== |
| // OnActivate |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnActivate( UINT nState, CWnd* pWndOther, BOOL bMinimized ) |
| { |
| // Always make the active window the "main" window so modal dialogs work better and the app quits after closing |
| // the last window. |
| |
| gApp.m_pMainWnd = this; |
| |
| CDialog::OnActivate(nState, pWndOther, bMinimized); |
| } |
| |
| //=========================================================================================================================== |
| // PostNcDestroy |
| //=========================================================================================================================== |
| |
| void ChooserDialog::PostNcDestroy() |
| { |
| // Call the base class to do the normal cleanup. |
| |
| delete this; |
| } |
| |
| //=========================================================================================================================== |
| // PreTranslateMessage |
| //=========================================================================================================================== |
| |
| BOOL ChooserDialog::PreTranslateMessage(MSG* pMsg) |
| { |
| BOOL result; |
| |
| result = false; |
| assert( mMenuAcceleratorTable ); |
| if( mMenuAcceleratorTable ) |
| { |
| result = ::TranslateAccelerator( m_hWnd, mMenuAcceleratorTable, pMsg ); |
| } |
| if( !result ) |
| { |
| result = CDialog::PreTranslateMessage( pMsg ); |
| } |
| return( result ); |
| } |
| |
| //=========================================================================================================================== |
| // OnInitMenuPopup |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnInitMenuPopup( CMenu *pPopupMenu, UINT nIndex, BOOL bSysMenu ) |
| { |
| CDialog::OnInitMenuPopup( pPopupMenu, nIndex, bSysMenu ); |
| |
| switch( nIndex ) |
| { |
| case kChooserMenuIndexFile: |
| break; |
| |
| case kChooserMenuIndexHelp: |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| //=========================================================================================================================== |
| // OnExit |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnExit() |
| { |
| OnClose(); |
| } |
| |
| //=========================================================================================================================== |
| // OnAbout |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnAbout() |
| { |
| AboutDialog dialog; |
| |
| dialog.DoModal(); |
| } |
| |
| //=========================================================================================================================== |
| // OnSysCommand |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnSysCommand( UINT inID, LPARAM inParam ) |
| { |
| CDialog::OnSysCommand( inID, inParam ); |
| } |
| |
| //=========================================================================================================================== |
| // OnClose |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnClose() |
| { |
| StopBrowsing(); |
| |
| gApp.m_pMainWnd = this; |
| DestroyWindow(); |
| } |
| |
| //=========================================================================================================================== |
| // OnNcDestroy |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnNcDestroy() |
| { |
| gApp.m_pMainWnd = this; |
| |
| CDialog::OnNcDestroy(); |
| } |
| |
| //=========================================================================================================================== |
| // OnDomainListChanged |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnDomainListChanged( NMHDR *pNMHDR, LRESULT *pResult ) |
| { |
| UNUSED_ALWAYS( pNMHDR ); |
| |
| // Domain list changes have similar effects to service list changes so reuse that code path by calling it here. |
| |
| OnServiceListChanged( NULL, NULL ); |
| |
| *pResult = 0; |
| } |
| |
| //=========================================================================================================================== |
| // OnServiceListChanged |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnServiceListChanged( NMHDR *pNMHDR, LRESULT *pResult ) |
| { |
| int selectedType; |
| int selectedDomain; |
| |
| UNUSED_ALWAYS( pNMHDR ); |
| |
| // Stop any existing service search. |
| |
| StopBrowsing(); |
| |
| // If a domain and service type are selected, start searching for the service type on the domain. |
| |
| selectedType = mServiceList.GetNextItem( -1, LVNI_SELECTED ); |
| selectedDomain = mDomainList.GetNextItem( -1, LVNI_SELECTED ); |
| |
| if( ( selectedType >= 0 ) && ( selectedDomain >= 0 ) ) |
| { |
| CString s; |
| std::string utf8; |
| const char * type; |
| |
| s = mDomainList.GetItemText( selectedDomain, 0 ); |
| StringObjectToUTF8String( s, utf8 ); |
| type = mServiceTypes[ selectedType ].serviceType.c_str(); |
| if( *type != '\0' ) |
| { |
| StartBrowsing( type, utf8.c_str() ); |
| } |
| } |
| |
| if( pResult ) |
| { |
| *pResult = 0; |
| } |
| } |
| |
| //=========================================================================================================================== |
| // OnChooserListChanged |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnChooserListChanged( NMHDR *pNMHDR, LRESULT *pResult ) |
| { |
| UNUSED_ALWAYS( pNMHDR ); |
| |
| UpdateInfoDisplay(); |
| *pResult = 0; |
| } |
| |
| //=========================================================================================================================== |
| // OnChooserListDoubleClick |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnChooserListDoubleClick( NMHDR *pNMHDR, LRESULT *pResult ) |
| { |
| int selectedItem; |
| |
| UNUSED_ALWAYS( pNMHDR ); |
| |
| // Display the service instance if it is selected. Otherwise, clear all the info. |
| |
| selectedItem = mChooserList.GetNextItem( -1, LVNI_SELECTED ); |
| if( selectedItem >= 0 ) |
| { |
| ServiceInstanceInfo * p; |
| CString url; |
| const KnownServiceEntry * service; |
| |
| assert( selectedItem < (int) mServiceInstances.size() ); |
| p = &mServiceInstances[ selectedItem ]; |
| |
| // Search for a known service type entry that matches. |
| |
| for( service = kKnownServiceTable; service->serviceType; ++service ) |
| { |
| if( p->type == service->serviceType ) |
| { |
| break; |
| } |
| } |
| if( service->serviceType ) |
| { |
| const char * text; |
| |
| // Create a URL representing the service instance. |
| |
| if( strcmp( service->serviceType, "_smb._tcp." ) == 0 ) |
| { |
| // Special case for SMB (no port number). |
| |
| url.Format( TEXT( "%s%s/" ), service->urlScheme, (const char *) p->ip.c_str() ); |
| } |
| else if( strcmp( service->serviceType, "_ftp._tcp." ) == 0 ) |
| { |
| // Special case for FTP to get login info. |
| |
| LoginDialog dialog; |
| CString username; |
| CString password; |
| |
| if( !dialog.GetLogin( username, password ) ) |
| { |
| goto exit; |
| } |
| |
| // Build URL in the following format: |
| // |
| // ftp://[username[:password]@]<ip> |
| |
| url += service->urlScheme; |
| if( username.GetLength() > 0 ) |
| { |
| url += username; |
| if( password.GetLength() > 0 ) |
| { |
| url += ':'; |
| url += password; |
| } |
| url += '@'; |
| } |
| url += p->ip.c_str(); |
| } |
| else if( strcmp( service->serviceType, "_http._tcp." ) == 0 ) |
| { |
| // Special case for HTTP to exclude "path=" if present. |
| |
| text = service->useText ? p->text.c_str() : ""; |
| if( strncmp( text, "path=", 5 ) == 0 ) |
| { |
| text += 5; |
| } |
| if( *text != '/' ) |
| { |
| url.Format( TEXT( "%s%s/%s" ), service->urlScheme, (const char *) p->ip.c_str(), text ); |
| } |
| else |
| { |
| url.Format( TEXT( "%s%s%s" ), service->urlScheme, (const char *) p->ip.c_str(), text ); |
| } |
| } |
| else |
| { |
| text = service->useText ? p->text.c_str() : ""; |
| url.Format( TEXT( "%s%s/%s" ), service->urlScheme, (const char *) p->ip.c_str(), text ); |
| } |
| |
| // Let the system open the URL in the correct app. |
| |
| { |
| CWaitCursor waitCursor; |
| |
| ShellExecute( NULL, TEXT( "open" ), url, TEXT( "" ), TEXT( "c:\\" ), SW_SHOWNORMAL ); |
| } |
| } |
| } |
| |
| exit: |
| *pResult = 0; |
| } |
| |
| //=========================================================================================================================== |
| // OnCancel |
| //=========================================================================================================================== |
| |
| void ChooserDialog::OnCancel() |
| { |
| // Do nothing. |
| } |
| |
| //=========================================================================================================================== |
| // PopulateServicesList |
| //=========================================================================================================================== |
| |
| void ChooserDialog::PopulateServicesList( void ) |
| { |
| ServiceTypeVector::iterator i; |
| CString type; |
| CString desc; |
| std::string tmp; |
| |
| // Add a fixed list of known services. |
| |
| if( mServiceTypes.empty() ) |
| { |
| const KnownServiceEntry * service; |
| |
| for( service = kKnownServiceTable; service->serviceType; ++service ) |
| { |
| ServiceTypeInfo info; |
| |
| info.serviceType = service->serviceType; |
| info.description = service->description; |
| info.urlScheme = service->urlScheme; |
| mServiceTypes.push_back( info ); |
| } |
| } |
| |
| // Add each service to the list. |
| |
| for( i = mServiceTypes.begin(); i != mServiceTypes.end(); ++i ) |
| { |
| const char * p; |
| const char * q; |
| |
| p = ( *i ).serviceType.c_str(); |
| if( *p == '_' ) ++p; // Skip leading '_'. |
| q = strchr( p, '.' ); // Find first '.'. |
| if( q ) tmp.assign( p, (size_t)( q - p ) ); // Use only up to the first '.'. |
| else tmp.assign( p ); // No '.' so use the entire string. |
| UTF8StringToStringObject( tmp.c_str(), type ); |
| UTF8StringToStringObject( ( *i ).description.c_str(), desc ); |
| |
| int n; |
| |
| n = mServiceList.GetItemCount(); |
| mServiceList.InsertItem( n, type ); |
| mServiceList.SetItemText( n, 1, desc ); |
| } |
| |
| // Select the first service type by default. |
| |
| if( !mServiceTypes.empty() ) |
| { |
| mServiceList.SetItemState( 0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED ); |
| } |
| } |
| |
| //=========================================================================================================================== |
| // UpdateInfoDisplay |
| //=========================================================================================================================== |
| |
| void ChooserDialog::UpdateInfoDisplay( void ) |
| { |
| int selectedItem; |
| std::string name; |
| CString s; |
| std::string ip; |
| std::string ifIP; |
| std::string text; |
| std::string textNewLines; |
| std::string hostName; |
| CWnd * item; |
| std::string::iterator i; |
| |
| // Display the service instance if it is selected. Otherwise, clear all the info. |
| |
| selectedItem = mChooserList.GetNextItem( -1, LVNI_SELECTED ); |
| if( selectedItem >= 0 ) |
| { |
| ServiceInstanceInfo * p; |
| |
| assert( selectedItem < (int) mServiceInstances.size() ); |
| p = &mServiceInstances[ selectedItem ]; |
| |
| name = p->name; |
| ip = p->ip; |
| ifIP = p->ifIP; |
| text = p->text; |
| hostName = p->hostName; |
| |
| // Sync up the list items with the actual data (IP address may change). |
| |
| UTF8StringToStringObject( ip.c_str(), s ); |
| mChooserList.SetItemText( selectedItem, 1, s ); |
| } |
| |
| // Name |
| |
| item = (CWnd *) this->GetDlgItem( IDC_INFO_NAME_TEXT ); |
| assert( item ); |
| UTF8StringToStringObject( name.c_str(), s ); |
| item->SetWindowText( s ); |
| |
| // IP |
| |
| item = (CWnd *) this->GetDlgItem( IDC_INFO_IP_TEXT ); |
| assert( item ); |
| UTF8StringToStringObject( ip.c_str(), s ); |
| item->SetWindowText( s ); |
| |
| // Interface |
| |
| item = (CWnd *) this->GetDlgItem( IDC_INFO_INTERFACE_TEXT ); |
| assert( item ); |
| UTF8StringToStringObject( ifIP.c_str(), s ); |
| item->SetWindowText( s ); |
| |
| |
| item = (CWnd *) this->GetDlgItem( IDC_INFO_HOST_NAME_TEXT ); |
| assert( item ); |
| UTF8StringToStringObject( hostName.c_str(), s ); |
| item->SetWindowText( s ); |
| |
| // Text |
| |
| item = (CWnd *) this->GetDlgItem( IDC_INFO_TEXT_TEXT ); |
| assert( item ); |
| for( i = text.begin(); i != text.end(); ++i ) |
| { |
| if( *i == '\1' ) |
| { |
| textNewLines += "\r\n"; |
| } |
| else |
| { |
| textNewLines += *i; |
| } |
| } |
| UTF8StringToStringObject( textNewLines.c_str(), s ); |
| item->SetWindowText( s ); |
| } |
| |
| #if 0 |
| #pragma mark - |
| #endif |
| |
| //=========================================================================================================================== |
| // OnDomainAdd |
| //=========================================================================================================================== |
| |
| LONG ChooserDialog::OnDomainAdd( WPARAM inWParam, LPARAM inLParam ) |
| { |
| DomainEventInfo * p; |
| std::auto_ptr < DomainEventInfo > pAutoPtr; |
| int n; |
| int i; |
| CString domain; |
| CString s; |
| bool found; |
| |
| UNUSED_ALWAYS( inWParam ); |
| |
| assert( inLParam ); |
| p = reinterpret_cast <DomainEventInfo *> ( inLParam ); |
| pAutoPtr.reset( p ); |
| |
| // Search to see if we already know about this domain. If not, add it to the list. |
| |
| found = false; |
| domain = p->domain; |
| n = mDomainList.GetItemCount(); |
| for( i = 0; i < n; ++i ) |
| { |
| s = mDomainList.GetItemText( i, 0 ); |
| if( s == domain ) |
| { |
| found = true; |
| break; |
| } |
| } |
| if( !found ) |
| { |
| int selectedItem; |
| |
| mDomainList.InsertItem( n, domain ); |
| |
| // If no domains are selected and the domain being added is a default domain, select it. |
| |
| selectedItem = mDomainList.GetNextItem( -1, LVNI_SELECTED ); |
| if( ( selectedItem < 0 ) && ( p->eventType == kDNSBrowserEventTypeAddDefaultDomain ) ) |
| { |
| mDomainList.SetItemState( n, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED ); |
| } |
| } |
| return( 0 ); |
| } |
| |
| //=========================================================================================================================== |
| // OnDomainRemove |
| //=========================================================================================================================== |
| |
| LONG ChooserDialog::OnDomainRemove( WPARAM inWParam, LPARAM inLParam ) |
| { |
| DomainEventInfo * p; |
| std::auto_ptr < DomainEventInfo > pAutoPtr; |
| int n; |
| int i; |
| CString domain; |
| CString s; |
| bool found; |
| |
| UNUSED_ALWAYS( inWParam ); |
| |
| assert( inLParam ); |
| p = reinterpret_cast <DomainEventInfo *> ( inLParam ); |
| pAutoPtr.reset( p ); |
| |
| // Search to see if we know about this domain. If so, remove it from the list. |
| |
| found = false; |
| domain = p->domain; |
| n = mDomainList.GetItemCount(); |
| for( i = 0; i < n; ++i ) |
| { |
| s = mDomainList.GetItemText( i, 0 ); |
| if( s == domain ) |
| { |
| found = true; |
| break; |
| } |
| } |
| if( found ) |
| { |
| mDomainList.DeleteItem( i ); |
| } |
| return( 0 ); |
| } |
| |
| //=========================================================================================================================== |
| // OnServiceAdd |
| //=========================================================================================================================== |
| |
| LONG ChooserDialog::OnServiceAdd( WPARAM inWParam, LPARAM inLParam ) |
| { |
| ServiceEventInfo * p; |
| std::auto_ptr < ServiceEventInfo > pAutoPtr; |
| |
| UNUSED_ALWAYS( inWParam ); |
| |
| assert( inLParam ); |
| p = reinterpret_cast <ServiceEventInfo *> ( inLParam ); |
| pAutoPtr.reset( p ); |
| |
| return( 0 ); |
| } |
| |
| //=========================================================================================================================== |
| // OnServiceRemove |
| //=========================================================================================================================== |
| |
| LONG ChooserDialog::OnServiceRemove( WPARAM inWParam, LPARAM inLParam ) |
| { |
| ServiceEventInfo * p; |
| std::auto_ptr < ServiceEventInfo > pAutoPtr; |
| bool found; |
| int n; |
| int i; |
| |
| UNUSED_ALWAYS( inWParam ); |
| |
| assert( inLParam ); |
| p = reinterpret_cast <ServiceEventInfo *> ( inLParam ); |
| pAutoPtr.reset( p ); |
| |
| // Search to see if we know about this service instance. If so, remove it from the list. |
| |
| found = false; |
| n = (int) mServiceInstances.size(); |
| for( i = 0; i < n; ++i ) |
| { |
| ServiceInstanceInfo * q; |
| |
| // If the name, type, domain, and interface match, treat it as the same service instance. |
| |
| q = &mServiceInstances[ i ]; |
| if( ( p->name == q->name ) && |
| ( p->type == q->type ) && |
| ( p->domain == q->domain ) ) |
| { |
| found = true; |
| break; |
| } |
| } |
| if( found ) |
| { |
| mChooserList.DeleteItem( i ); |
| assert( i < (int) mServiceInstances.size() ); |
| mServiceInstances.erase( mServiceInstances.begin() + i ); |
| } |
| return( 0 ); |
| } |
| |
| //=========================================================================================================================== |
| // OnResolve |
| //=========================================================================================================================== |
| |
| LONG ChooserDialog::OnResolve( WPARAM inWParam, LPARAM inLParam ) |
| { |
| ServiceInstanceInfo * p; |
| std::auto_ptr < ServiceInstanceInfo > pAutoPtr; |
| int selectedType; |
| int n; |
| int i; |
| bool found; |
| |
| UNUSED_ALWAYS( inWParam ); |
| |
| assert( inLParam ); |
| p = reinterpret_cast <ServiceInstanceInfo *> ( inLParam ); |
| pAutoPtr.reset( p ); |
| |
| // Make sure it is for an item of the correct type. This handles any resolves that may have been queued up. |
| |
| selectedType = mServiceList.GetNextItem( -1, LVNI_SELECTED ); |
| assert( selectedType >= 0 ); |
| if( selectedType >= 0 ) |
| { |
| assert( selectedType <= (int) mServiceTypes.size() ); |
| if( p->type != mServiceTypes[ selectedType ].serviceType ) |
| { |
| goto exit; |
| } |
| } |
| |
| // Search to see if we know about this service instance. If so, update its info. Otherwise, add it to the list. |
| |
| found = false; |
| n = (int) mServiceInstances.size(); |
| for( i = 0; i < n; ++i ) |
| { |
| ServiceInstanceInfo * q; |
| |
| // If the name, type, domain, and interface matches, treat it as the same service instance. |
| |
| q = &mServiceInstances[ i ]; |
| if( ( p->name == q->name ) && |
| ( p->type == q->type ) && |
| ( p->domain == q->domain ) && |
| ( p->ifIP == q->ifIP ) ) |
| { |
| found = true; |
| break; |
| } |
| } |
| if( found ) |
| { |
| mServiceInstances[ i ] = *p; |
| } |
| else |
| { |
| CString s; |
| |
| mServiceInstances.push_back( *p ); |
| UTF8StringToStringObject( p->name.c_str(), s ); |
| mChooserList.InsertItem( n, s ); |
| |
| UTF8StringToStringObject( p->ip.c_str(), s ); |
| mChooserList.SetItemText( n, 1, s ); |
| |
| // If this is the only item, select it. |
| |
| if( n == 0 ) |
| { |
| mChooserList.SetItemState( n, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED ); |
| } |
| } |
| UpdateInfoDisplay(); |
| |
| exit: |
| return( 0 ); |
| } |
| |
| //=========================================================================================================================== |
| // StartBrowsing |
| //=========================================================================================================================== |
| |
| void ChooserDialog::StartBrowsing( const char *inType, const char *inDomain ) |
| { |
| DNSStatus err; |
| |
| assert( mServiceInstances.empty() ); |
| assert( mChooserList.GetItemCount() == 0 ); |
| assert( !mIsServiceBrowsing ); |
| |
| mChooserList.DeleteAllItems(); |
| mServiceInstances.clear(); |
| |
| mIsServiceBrowsing = true; |
| err = DNSBrowserStartServiceSearch( mBrowser, kDNSBrowserFlagAutoResolve, inType, inDomain ); |
| assert( err == kDNSNoErr ); |
| } |
| |
| //=========================================================================================================================== |
| // StopBrowsing |
| //=========================================================================================================================== |
| |
| void ChooserDialog::StopBrowsing( void ) |
| { |
| // If searching, stop. |
| |
| if( mIsServiceBrowsing ) |
| { |
| DNSStatus err; |
| |
| mIsServiceBrowsing = false; |
| err = DNSBrowserStopServiceSearch( mBrowser, 0 ); |
| assert( err == kDNSNoErr ); |
| } |
| |
| // Remove all service instances. |
| |
| mChooserList.DeleteAllItems(); |
| assert( mChooserList.GetItemCount() == 0 ); |
| mServiceInstances.clear(); |
| assert( mServiceInstances.empty() ); |
| UpdateInfoDisplay(); |
| } |
| |
| #if 0 |
| #pragma mark - |
| #endif |
| |
| //=========================================================================================================================== |
| // BrowserCallBack |
| //=========================================================================================================================== |
| |
| static void |
| BrowserCallBack( |
| void * inContext, |
| DNSBrowserRef inRef, |
| DNSStatus inStatusCode, |
| const DNSBrowserEvent * inEvent ) |
| { |
| ChooserDialog * dialog; |
| UINT message; |
| BOOL posted; |
| |
| UNUSED_ALWAYS( inStatusCode ); |
| UNUSED_ALWAYS( inRef ); |
| |
| // Check parameters. |
| |
| assert( inContext ); |
| dialog = reinterpret_cast <ChooserDialog *> ( inContext ); |
| |
| try |
| { |
| switch( inEvent->type ) |
| { |
| case kDNSBrowserEventTypeRelease: |
| break; |
| |
| // Domains |
| |
| case kDNSBrowserEventTypeAddDomain: |
| case kDNSBrowserEventTypeAddDefaultDomain: |
| case kDNSBrowserEventTypeRemoveDomain: |
| { |
| DomainEventInfo * domain; |
| std::auto_ptr < DomainEventInfo > domainAutoPtr; |
| |
| domain = new DomainEventInfo; |
| domainAutoPtr.reset( domain ); |
| |
| domain->eventType = inEvent->type; |
| domain->domain = inEvent->data.addDomain.domain; |
| domain->ifIP = inEvent->data.addDomain.interfaceIP; |
| |
| message = ( inEvent->type == kDNSBrowserEventTypeRemoveDomain ) ? WM_USER_DOMAIN_REMOVE : WM_USER_DOMAIN_ADD; |
| posted = ::PostMessage( dialog->GetSafeHwnd(), message, 0, (LPARAM) domain ); |
| assert( posted ); |
| if( posted ) |
| { |
| domainAutoPtr.release(); |
| } |
| break; |
| } |
| |
| // Services |
| |
| case kDNSBrowserEventTypeAddService: |
| case kDNSBrowserEventTypeRemoveService: |
| { |
| ServiceEventInfo * service; |
| std::auto_ptr < ServiceEventInfo > serviceAutoPtr; |
| |
| service = new ServiceEventInfo; |
| serviceAutoPtr.reset( service ); |
| |
| service->eventType = inEvent->type; |
| service->name = inEvent->data.addService.name; |
| service->type = inEvent->data.addService.type; |
| service->domain = inEvent->data.addService.domain; |
| service->ifIP = inEvent->data.addService.interfaceIP; |
| |
| message = ( inEvent->type == kDNSBrowserEventTypeAddService ) ? WM_USER_SERVICE_ADD : WM_USER_SERVICE_REMOVE; |
| posted = ::PostMessage( dialog->GetSafeHwnd(), message, 0, (LPARAM) service ); |
| assert( posted ); |
| if( posted ) |
| { |
| serviceAutoPtr.release(); |
| } |
| break; |
| } |
| |
| // Resolves |
| |
| case kDNSBrowserEventTypeResolved: |
| if( inEvent->data.resolved->address.addressType == kDNSNetworkAddressTypeIPv4 ) |
| { |
| ServiceInstanceInfo * serviceInstance; |
| std::auto_ptr < ServiceInstanceInfo > serviceInstanceAutoPtr; |
| char s[ 32 ]; |
| |
| serviceInstance = new ServiceInstanceInfo; |
| serviceInstanceAutoPtr.reset( serviceInstance ); |
| |
| serviceInstance->name = inEvent->data.resolved->name; |
| serviceInstance->type = inEvent->data.resolved->type; |
| serviceInstance->domain = inEvent->data.resolved->domain; |
| serviceInstance->ip = DNSNetworkAddressToString( &inEvent->data.resolved->address, s ); |
| serviceInstance->ifIP = DNSNetworkAddressToString( &inEvent->data.resolved->interfaceIP, s ); |
| serviceInstance->text = inEvent->data.resolved->textRecord; |
| serviceInstance->hostName = inEvent->data.resolved->hostName; |
| |
| posted = ::PostMessage( dialog->GetSafeHwnd(), WM_USER_RESOLVE, 0, (LPARAM) serviceInstance ); |
| assert( posted ); |
| if( posted ) |
| { |
| serviceInstanceAutoPtr.release(); |
| } |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| catch( ... ) |
| { |
| // Don't let exceptions escape. |
| } |
| } |
| |
| //=========================================================================================================================== |
| // DNSNetworkAddressToString |
| // |
| // Note: Currently only supports IPv4 network addresses. |
| //=========================================================================================================================== |
| |
| static char * DNSNetworkAddressToString( const DNSNetworkAddress *inAddr, char *outString ) |
| { |
| const DNSUInt8 * p; |
| DNSUInt16 port; |
| |
| p = inAddr->u.ipv4.addr.v8; |
| port = ntohs( inAddr->u.ipv4.port.v16 ); |
| if( port != kDNSPortInvalid ) |
| { |
| sprintf( outString, "%u.%u.%u.%u:%u", p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ], port ); |
| } |
| else |
| { |
| sprintf( outString, "%u.%u.%u.%u", p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ] ); |
| } |
| return( outString ); |
| } |
| |
| //=========================================================================================================================== |
| // UTF8StringToStringObject |
| //=========================================================================================================================== |
| |
| static DWORD UTF8StringToStringObject( const char *inUTF8, CString &inObject ) |
| { |
| DWORD err; |
| int n; |
| BSTR unicode; |
| |
| unicode = NULL; |
| |
| n = MultiByteToWideChar( CP_UTF8, 0, inUTF8, -1, NULL, 0 ); |
| if( n > 0 ) |
| { |
| unicode = (BSTR) malloc( (size_t)( n * sizeof( wchar_t ) ) ); |
| if( !unicode ) |
| { |
| err = ERROR_INSUFFICIENT_BUFFER; |
| goto exit; |
| } |
| |
| n = MultiByteToWideChar( CP_UTF8, 0, inUTF8, -1, unicode, n ); |
| try |
| { |
| inObject = unicode; |
| } |
| catch( ... ) |
| { |
| err = ERROR_NO_UNICODE_TRANSLATION; |
| goto exit; |
| } |
| } |
| else |
| { |
| inObject = ""; |
| } |
| err = 0; |
| |
| exit: |
| if( unicode ) |
| { |
| free( unicode ); |
| } |
| return( err ); |
| } |
| |
| //=========================================================================================================================== |
| // StringObjectToUTF8String |
| //=========================================================================================================================== |
| |
| static DWORD StringObjectToUTF8String( CString &inObject, std::string &outUTF8 ) |
| { |
| DWORD err; |
| BSTR unicode; |
| int nUnicode; |
| int n; |
| char * utf8; |
| |
| unicode = NULL; |
| utf8 = NULL; |
| |
| nUnicode = inObject.GetLength(); |
| if( nUnicode > 0 ) |
| { |
| unicode = inObject.AllocSysString(); |
| n = WideCharToMultiByte( CP_UTF8, 0, unicode, nUnicode, NULL, 0, NULL, NULL ); |
| assert( n > 0 ); |
| |
| utf8 = (char *) malloc( (size_t) n ); |
| assert( utf8 ); |
| if( !utf8 ) { err = ERROR_INSUFFICIENT_BUFFER; goto exit; } |
| |
| n = WideCharToMultiByte( CP_UTF8, 0, unicode, nUnicode, utf8, n, NULL, NULL ); |
| assert( n > 0 ); |
| |
| try |
| { |
| outUTF8.assign( utf8, n ); |
| } |
| catch( ... ) |
| { |
| err = ERROR_NO_UNICODE_TRANSLATION; |
| goto exit; |
| } |
| } |
| else |
| { |
| outUTF8.clear(); |
| } |
| err = 0; |
| |
| exit: |
| if( unicode ) |
| { |
| SysFreeString( unicode ); |
| } |
| if( utf8 ) |
| { |
| free( utf8 ); |
| } |
| return( err ); |
| } |