/* * Copyright (c) 2003-2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ Change History (most recent first): $Log: ExplorerBarWindow.cpp,v $ Revision 1.12 2004/10/26 00:56:03 cheshire Use "#if 0" instead of commenting out code Revision 1.11 2004/10/18 23:49:17 shersche Remove trailing dot from hostname, because some flavors of Windows have difficulty parsing hostnames with a trailing dot. Bug #: 3841564 Revision 1.10 2004/09/02 02:18:58 cheshire Minor textual cleanup to improve readability Revision 1.9 2004/09/02 02:11:56 cheshire Fix incorrect testing of MoreComing flag Revision 1.8 2004/07/26 05:47:31 shersche use TXTRecord APIs, fix bug in locating service to be removed Revision 1.7 2004/07/22 16:08:20 shersche clean up debug print statements, re-enable code inadvertently commented out Revision 1.6 2004/07/22 05:27:20 shersche Check to make sure error isn't WSAEWOULDBLOCK when canceling browse Bug #: 3735827 Revision 1.5 2004/07/20 06:49:18 shersche clean up socket handling code Revision 1.4 2004/07/13 21:24:21 rpantos Fix for . Revision 1.3 2004/06/27 14:59:59 shersche reference count service info to handle multi-homed hosts Revision 1.2 2004/06/23 16:09:34 shersche Add the resolve DNSServiceRef to list of extant refs. This fixes the "doesn't resolve when double clicking" problem Submitted by: Scott Herscher Revision 1.1 2004/06/18 04:34:59 rpantos Move to Clients from mDNSWindows Revision 1.5 2004/04/15 01:00:05 bradley Removed support for automatically querying for A/AAAA records when resolving names. Platforms without .local name resolving support will need to manually query for A/AAAA records as needed. Revision 1.4 2004/04/09 21:03:15 bradley Changed port numbers to use network byte order for consistency with other platforms. Revision 1.3 2004/04/08 09:43:43 bradley Changed callback calling conventions to __stdcall so they can be used with C# delegates. Revision 1.2 2004/02/21 04:36:19 bradley Enable dot local name lookups now that the NSP is being installed. Revision 1.1 2004/01/30 03:01:56 bradley Explorer Plugin to browse for DNS-SD advertised Web and FTP servers from within Internet Explorer. */ #include "StdAfx.h" #include "CommonServices.h" #include "DebugServices.h" #include "WinServices.h" #include "dns_sd.h" #include "ExplorerBar.h" #include "LoginDialog.h" #include "Resource.h" #include "ExplorerBarWindow.h" // MFC Debugging #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #if 0 #pragma mark == Constants == #endif //=========================================================================================================================== // Constants //=========================================================================================================================== // Control IDs #define IDC_EXPLORER_TREE 1234 // Private Messages #define WM_PRIVATE_SERVICE_EVENT ( WM_USER + 0x100 ) // TXT records #define kTXTRecordKeyPath "path" #if 0 #pragma mark == Prototypes == #endif //=========================================================================================================================== // Prototypes //=========================================================================================================================== DEBUG_LOCAL int FindServiceArrayIndex( const ServiceInfoArray &inArray, const ServiceInfo &inService, int &outIndex ); #if 0 #pragma mark == Message Map == #endif //=========================================================================================================================== // Message Map //=========================================================================================================================== BEGIN_MESSAGE_MAP( ExplorerBarWindow, CWnd ) ON_WM_CREATE() ON_WM_DESTROY() ON_WM_SIZE() ON_NOTIFY( NM_DBLCLK, IDC_EXPLORER_TREE, OnDoubleClick ) ON_MESSAGE( WM_PRIVATE_SERVICE_EVENT, OnServiceEvent ) END_MESSAGE_MAP() #if 0 #pragma mark - #endif //=========================================================================================================================== // ExplorerBarWindow //=========================================================================================================================== ExplorerBarWindow::ExplorerBarWindow( void ) { mOwner = NULL; mResolveServiceRef = NULL; } //=========================================================================================================================== // ~ExplorerBarWindow //=========================================================================================================================== ExplorerBarWindow::~ExplorerBarWindow( void ) { // } #if 0 #pragma mark - #endif //=========================================================================================================================== // OnCreate //=========================================================================================================================== int ExplorerBarWindow::OnCreate( LPCREATESTRUCT inCreateStruct ) { AFX_MANAGE_STATE( AfxGetStaticModuleState() ); OSStatus err; CRect rect; CBitmap bitmap; CString s; err = CWnd::OnCreate( inCreateStruct ); require_noerr( err, exit ); GetClientRect( rect ); mTree.Create( WS_TABSTOP | WS_VISIBLE | WS_CHILD | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_HASLINES | TVS_NOHSCROLL , rect, this, IDC_EXPLORER_TREE ); ServiceHandlerEntry * e; // Web Site Handler e = new ServiceHandlerEntry; check( e ); e->type = "_http._tcp"; e->urlScheme = "http://"; e->ref = NULL; e->treeItem = NULL; e->treeFirst = true; e->obj = this; e->needsLogin = false; mServiceHandlers.Add( e ); s.LoadString( IDS_WEB_SITES ); e->treeItem = mTree.InsertItem( s, 0, 0 ); mTree.Expand( e->treeItem, TVE_EXPAND ); err = DNSServiceBrowse( &e->ref, 0, 0, e->type, NULL, BrowseCallBack, e ); require_noerr( err, exit ); err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD(e->ref), m_hWnd, WM_PRIVATE_SERVICE_EVENT, FD_READ|FD_CLOSE); require_noerr( err, exit ); m_serviceRefs.push_back(e->ref); // FTP Site Handler e = new ServiceHandlerEntry; check( e ); e->type = "_ftp._tcp"; e->urlScheme = "ftp://"; e->ref = NULL; e->treeItem = NULL; e->treeFirst = true; e->obj = this; e->needsLogin = true; mServiceHandlers.Add( e ); s.LoadString( IDS_FTP_SITES ); e->treeItem = mTree.InsertItem( s, 0, 0 ); mTree.Expand( e->treeItem, TVE_EXPAND ); err = DNSServiceBrowse( &e->ref, 0, 0, e->type, NULL, BrowseCallBack, e ); require_noerr( err, exit ); err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD(e->ref), m_hWnd, WM_PRIVATE_SERVICE_EVENT, FD_READ|FD_CLOSE); require_noerr( err, exit ); m_serviceRefs.push_back(e->ref); m_imageList.Create(16, 16, ILC_COLORDDB, 1, 0); bitmap.LoadBitmap(IDB_LOGO); m_imageList.Add(&bitmap, (CBitmap*) NULL); mTree.SetImageList(&m_imageList, TVSIL_NORMAL); exit: // Cannot talk to the mDNSResponder service. Show the error message and exit (with kNoErr so they can see it). if ( err != kNoErr ) { s.LoadString( IDS_MDNSRESPONDER_NOT_AVAILABLE ); mTree.DeleteAllItems(); mTree.InsertItem( s, 0, 0, TVI_ROOT, TVI_LAST ); err = kNoErr; } return( err ); } //=========================================================================================================================== // OnDestroy //=========================================================================================================================== void ExplorerBarWindow::OnDestroy( void ) { // Stop any resolves that may still be pending (shouldn't be any). StopResolve(); // Clean up the extant browses while (m_serviceRefs.size() > 0) { // // take the head of the list // DNSServiceRef ref = m_serviceRefs.front(); // // Stop will remove it from the list // Stop( ref ); } // Clean up the service handlers. int i; int n; n = (int) mServiceHandlers.GetSize(); for( i = 0; i < n; ++i ) { delete mServiceHandlers[ i ]; } CWnd::OnDestroy(); } //=========================================================================================================================== // OnSize //=========================================================================================================================== void ExplorerBarWindow::OnSize( UINT inType, int inX, int inY ) { CWnd::OnSize( inType, inX, inY ); mTree.MoveWindow( 0, 0, inX, inY ); } //=========================================================================================================================== // OnDoubleClick //=========================================================================================================================== void ExplorerBarWindow::OnDoubleClick( NMHDR *inNMHDR, LRESULT *outResult ) { HTREEITEM item; ServiceInfo * service; OSStatus err; DEBUG_UNUSED( inNMHDR ); item = mTree.GetSelectedItem(); require( item, exit ); service = reinterpret_cast < ServiceInfo * > ( mTree.GetItemData( item ) ); require_quiet( service, exit ); err = StartResolve( service ); require_noerr( err, exit ); exit: *outResult = 0; } //=========================================================================================================================== // OnServiceEvent //=========================================================================================================================== LONG ExplorerBarWindow::OnServiceEvent(WPARAM inWParam, LPARAM inLParam) { if (WSAGETSELECTERROR(inLParam) && !(HIWORD(inLParam))) { dlog( kDebugLevelError, "OnServiceEvent: window error\n" ); } else { SOCKET sock = (SOCKET) inWParam; // iterate thru list ServiceRefList::iterator it; for (it = m_serviceRefs.begin(); it != m_serviceRefs.end(); it++) { DNSServiceRef ref = *it; check(ref != NULL); if ((SOCKET) DNSServiceRefSockFD(ref) == sock) { DNSServiceErrorType err; err = DNSServiceProcessResult(ref); if (err != 0) { CString s; s.LoadString( IDS_MDNSRESPONDER_NOT_AVAILABLE ); mTree.DeleteAllItems(); mTree.InsertItem( s, 0, 0, TVI_ROOT, TVI_LAST ); Stop(ref); } break; } } } return ( 0 ); } #if 0 #pragma mark - #endif //=========================================================================================================================== // BrowseCallBack //=========================================================================================================================== void DNSSD_API ExplorerBarWindow::BrowseCallBack( DNSServiceRef inRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inErrorCode, const char * inName, const char * inType, const char * inDomain, void * inContext ) { ServiceHandlerEntry * obj; ServiceInfo * service; OSStatus err; DEBUG_UNUSED( inRef ); obj = NULL; service = NULL; require_noerr( inErrorCode, exit ); obj = reinterpret_cast < ServiceHandlerEntry * > ( inContext ); check( obj ); check( obj->obj ); // // set the UI to hold off on updates // obj->obj->mTree.SetRedraw(FALSE); try { service = new ServiceInfo; require_action( service, exit, err = kNoMemoryErr ); err = UTF8StringToStringObject( inName, service->displayName ); check_noerr( err ); service->name = strdup( inName ); require_action( service->name, exit, err = kNoMemoryErr ); service->type = strdup( inType ); require_action( service->type, exit, err = kNoMemoryErr ); service->domain = strdup( inDomain ); require_action( service->domain, exit, err = kNoMemoryErr ); service->ifi = inInterfaceIndex; service->handler = obj; service->refs = 1; if (inFlags & kDNSServiceFlagsAdd) obj->obj->OnServiceAdd (service); else obj->obj->OnServiceRemove(service); service = NULL; } catch( ... ) { dlog( kDebugLevelError, "BrowseCallBack: exception thrown\n" ); } exit: // // If no more coming, then update UI // if (obj && obj->obj && ((inFlags & kDNSServiceFlagsMoreComing) == 0)) { obj->obj->mTree.SetRedraw(TRUE); obj->obj->mTree.Invalidate(); } if( service ) { delete service; } } //=========================================================================================================================== // OnServiceAdd //=========================================================================================================================== LONG ExplorerBarWindow::OnServiceAdd( ServiceInfo * service ) { ServiceHandlerEntry * handler; int cmp; int index; check( service ); handler = service->handler; check( handler ); cmp = FindServiceArrayIndex( handler->array, *service, index ); if( cmp == 0 ) { // Found a match so update the item. The index is index + 1 so subtract 1. index -= 1; check( index < handler->array.GetSize() ); handler->array[ index ]->refs++; delete service; } else { HTREEITEM afterItem; // Insert the new item in sorted order. afterItem = ( index > 0 ) ? handler->array[ index - 1 ]->item : TVI_FIRST; handler->array.InsertAt( index, service ); service->item = mTree.InsertItem( service->displayName, handler->treeItem, afterItem ); mTree.SetItemData( service->item, (DWORD_PTR) service ); // Make sure the item is visible if this is the first time a service was added. if( handler->treeFirst ) { handler->treeFirst = false; mTree.EnsureVisible( service->item ); } } return( 0 ); } //=========================================================================================================================== // OnServiceRemove //=========================================================================================================================== LONG ExplorerBarWindow::OnServiceRemove( ServiceInfo * service ) { ServiceHandlerEntry * handler; int cmp; int index; check( service ); handler = service->handler; check( handler ); // Search to see if we know about this service instance. If so, remove it from the list. cmp = FindServiceArrayIndex( handler->array, *service, index ); check( cmp == 0 ); if( cmp == 0 ) { // Possibly found a match remove the item. The index // is index + 1 so subtract 1. index -= 1; check( index < handler->array.GetSize() ); if ( --handler->array[ index ]->refs == 0 ) { mTree.DeleteItem( handler->array[ index ]->item ); delete handler->array[ index ]; handler->array.RemoveAt( index ); } } delete service; return( 0 ); } #if 0 #pragma mark - #endif //=========================================================================================================================== // StartResolve //=========================================================================================================================== OSStatus ExplorerBarWindow::StartResolve( ServiceInfo *inService ) { OSStatus err; check( inService ); // Stop any current resolve that may be in progress. StopResolve(); // Resolve the service. err = DNSServiceResolve( &mResolveServiceRef, 0, 0, inService->name, inService->type, inService->domain, (DNSServiceResolveReply) ResolveCallBack, inService->handler ); require_noerr( err, exit ); err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD(mResolveServiceRef), m_hWnd, WM_PRIVATE_SERVICE_EVENT, FD_READ|FD_CLOSE); require_noerr( err, exit ); m_serviceRefs.push_back(mResolveServiceRef); exit: return( err ); } //=========================================================================================================================== // StopResolve //=========================================================================================================================== void ExplorerBarWindow::StopResolve( void ) { if( mResolveServiceRef ) { Stop( mResolveServiceRef ); mResolveServiceRef = NULL; } } //=========================================================================================================================== // ResolveCallBack //=========================================================================================================================== void DNSSD_API ExplorerBarWindow::ResolveCallBack( DNSServiceRef inRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inErrorCode, const char * inFullName, const char * inHostName, uint16_t inPort, uint16_t inTXTSize, const char * inTXT, void * inContext ) { ExplorerBarWindow * obj; ServiceHandlerEntry * handler; OSStatus err; DEBUG_UNUSED( inRef ); DEBUG_UNUSED( inFlags ); DEBUG_UNUSED( inErrorCode ); DEBUG_UNUSED( inFullName ); require_noerr( inErrorCode, exit ); handler = (ServiceHandlerEntry *) inContext; check( handler ); obj = handler->obj; check( obj ); try { ResolveInfo * resolve; int idx; dlog( kDebugLevelNotice, "resolved %s on ifi %d to %s\n", inFullName, inInterfaceIndex, inHostName ); // Stop resolving after the first good result. obj->StopResolve(); // Post a message to the main thread so it can handle it since MFC is not thread safe. resolve = new ResolveInfo; require_action( resolve, exit, err = kNoMemoryErr ); UTF8StringToStringObject( inHostName, resolve->host ); // rdar://problem/3841564 // // strip trailing dot from hostname because some flavors of Windows // have trouble parsing it. idx = resolve->host.ReverseFind('.'); if ((idx > 1) && ((resolve->host.GetLength() - 1) == idx)) { resolve->host.Delete(idx, 1); } resolve->port = ntohs( inPort ); resolve->ifi = inInterfaceIndex; resolve->handler = handler; err = resolve->txt.SetData( inTXT, inTXTSize ); check_noerr( err ); obj->OnResolve(resolve); } catch( ... ) { dlog( kDebugLevelError, "ResolveCallBack: exception thrown\n" ); } exit: return; } //=========================================================================================================================== // OnResolve //=========================================================================================================================== LONG ExplorerBarWindow::OnResolve( ResolveInfo * resolve ) { CString url; uint8_t * path; uint8_t pathSize; char * pathPrefix; CString username; CString password; check( resolve ); // Get login info if needed. if( resolve->handler->needsLogin ) { LoginDialog dialog; if( !dialog.GetLogin( username, password ) ) { goto exit; } } // If the HTTP TXT record is a "path=" entry, use it as the resource path. Otherwise, use "/". pathPrefix = ""; if( strcmp( resolve->handler->type, "_http._tcp" ) == 0 ) { uint8_t * txtData; uint16_t txtLen; resolve->txt.GetData( &txtData, &txtLen ); path = (uint8_t*) TXTRecordGetValuePtr(txtLen, txtData, kTXTRecordKeyPath, &pathSize); if (path == NULL) { path = (uint8_t*) ""; pathSize = 1; } } else { path = (uint8_t *) ""; pathSize = 1; } // Build the URL in the following format: // // [[:]@][] url.AppendFormat( TEXT( "%S" ), resolve->handler->urlScheme ); // URL Scheme if( username.GetLength() > 0 ) { url.AppendFormat( TEXT( "%s" ), username ); // Username if( password.GetLength() > 0 ) { url.AppendFormat( TEXT( ":%s" ), password ); // Password } url.AppendFormat( TEXT( "@" ) ); } url += resolve->host; // Host url.AppendFormat( TEXT( ":%d" ), resolve->port ); // :Port url.AppendFormat( TEXT( "%S" ), pathPrefix ); // Path Prefix ("/" or empty). url.AppendFormat( TEXT( "%.*S" ), (int) pathSize, (char *) path ); // Path (possibly empty). // Tell Internet Explorer to go to the URL. check( mOwner ); mOwner->GoToURL( url ); exit: delete resolve; return( 0 ); } //=========================================================================================================================== // Stop //=========================================================================================================================== void ExplorerBarWindow::Stop( DNSServiceRef ref ) { m_serviceRefs.remove( ref ); WSAAsyncSelect(DNSServiceRefSockFD( ref ), m_hWnd, WM_PRIVATE_SERVICE_EVENT, 0); DNSServiceRefDeallocate( ref ); } #if 0 #pragma mark - #endif //=========================================================================================================================== // FindServiceArrayIndex //=========================================================================================================================== DEBUG_LOCAL int FindServiceArrayIndex( const ServiceInfoArray &inArray, const ServiceInfo &inService, int &outIndex ) { int result; int lo; int hi; int mid; result = -1; mid = 0; lo = 0; hi = (int)( inArray.GetSize() - 1 ); while( lo <= hi ) { mid = ( lo + hi ) / 2; result = inService.displayName.CompareNoCase( inArray[ mid ]->displayName ); #if 0 if( result == 0 ) { result = ( (int) inService.ifi ) - ( (int) inArray[ mid ]->ifi ); } #endif if( result == 0 ) { break; } else if( result < 0 ) { hi = mid - 1; } else { lo = mid + 1; } } if( result == 0 ) { mid += 1; // Bump index so new item is inserted after matching item. } else if( result > 0 ) { mid += 1; } outIndex = mid; return( result ); }