1/* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2003-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	"StdAfx.h"
19
20#include	"CommonServices.h"
21#include	"DebugServices.h"
22#include	"WinServices.h"
23#include	"dns_sd.h"
24
25#include	"ExplorerBar.h"
26#include	"LoginDialog.h"
27#include	"Resource.h"
28
29#include	"ExplorerBarWindow.h"
30#include	"ExplorerPlugin.h"
31
32// MFC Debugging
33
34#ifdef _DEBUG
35#define new DEBUG_NEW
36#undef THIS_FILE
37static char THIS_FILE[] = __FILE__;
38#endif
39
40#if 0
41#pragma mark == Constants ==
42#endif
43
44//===========================================================================================================================
45//	Constants
46//===========================================================================================================================
47
48// Control IDs
49
50#define	IDC_EXPLORER_TREE				1234
51
52// Private Messages
53
54#define WM_PRIVATE_SERVICE_EVENT				( WM_USER + 0x100 )
55
56// TXT records
57
58#define	kTXTRecordKeyPath				"path"
59
60// IE Icon resource
61
62#define kIEIconResource					32529
63
64
65#if 0
66#pragma mark == Prototypes ==
67#endif
68
69//===========================================================================================================================
70//	Prototypes
71//===========================================================================================================================
72
73DEBUG_LOCAL int			FindServiceArrayIndex( const ServiceInfoArray &inArray, const ServiceInfo &inService, int &outIndex );
74
75#if 0
76#pragma mark == Message Map ==
77#endif
78
79//===========================================================================================================================
80//	Message Map
81//===========================================================================================================================
82
83BEGIN_MESSAGE_MAP( ExplorerBarWindow, CWnd )
84	ON_WM_CREATE()
85	ON_WM_DESTROY()
86	ON_WM_SIZE()
87	ON_NOTIFY( NM_DBLCLK, IDC_EXPLORER_TREE, OnDoubleClick )
88	ON_MESSAGE( WM_PRIVATE_SERVICE_EVENT, OnServiceEvent )
89END_MESSAGE_MAP()
90
91#if 0
92#pragma mark -
93#endif
94
95//===========================================================================================================================
96//	ExplorerBarWindow
97//===========================================================================================================================
98
99ExplorerBarWindow::ExplorerBarWindow( void )
100{
101	mOwner				= NULL;
102	mResolveServiceRef	= NULL;
103}
104
105//===========================================================================================================================
106//	~ExplorerBarWindow
107//===========================================================================================================================
108
109ExplorerBarWindow::~ExplorerBarWindow( void )
110{
111	//
112}
113
114#if 0
115#pragma mark -
116#endif
117
118//===========================================================================================================================
119//	OnCreate
120//===========================================================================================================================
121
122int	ExplorerBarWindow::OnCreate( LPCREATESTRUCT inCreateStruct )
123{
124	AFX_MANAGE_STATE( AfxGetStaticModuleState() );
125
126	HINSTANCE		module = NULL;
127	OSStatus		err;
128	CRect			rect;
129	CBitmap			bitmap;
130	CString			s;
131
132	err = CWnd::OnCreate( inCreateStruct );
133	require_noerr( err, exit );
134
135	GetClientRect( rect );
136	mTree.Create( WS_TABSTOP | WS_VISIBLE | WS_CHILD | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_NOHSCROLL , rect, this,
137		IDC_EXPLORER_TREE );
138
139	ServiceHandlerEntry *		e;
140
141	s.LoadString( IDS_ABOUT );
142	m_about = mTree.InsertItem( s, 0, 0 );
143
144	// Web Site Handler
145
146	e = new ServiceHandlerEntry;
147	check( e );
148	e->type				= "_http._tcp";
149	e->urlScheme		= "http://";
150	e->ref				= NULL;
151	e->obj				= this;
152	e->needsLogin		= false;
153	mServiceHandlers.Add( e );
154
155	err = DNSServiceBrowse( &e->ref, 0, 0, e->type, NULL, BrowseCallBack, e );
156	require_noerr( err, exit );
157
158	err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD(e->ref), m_hWnd, WM_PRIVATE_SERVICE_EVENT, FD_READ|FD_CLOSE);
159	require_noerr( err, exit );
160
161	m_serviceRefs.push_back(e->ref);
162
163#if defined( _BROWSE_FOR_HTTPS_ )
164	e = new ServiceHandlerEntry;
165	check( e );
166	e->type				= "_https._tcp";
167	e->urlScheme		= "https://";
168	e->ref				= NULL;
169	e->obj				= this;
170	e->needsLogin		= false;
171	mServiceHandlers.Add( e );
172
173	err = DNSServiceBrowse( &e->ref, 0, 0, e->type, NULL, BrowseCallBack, e );
174	require_noerr( err, exit );
175
176	err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD(e->ref), m_hWnd, WM_PRIVATE_SERVICE_EVENT, FD_READ|FD_CLOSE);
177	require_noerr( err, exit );
178
179	m_serviceRefs.push_back(e->ref);
180#endif
181
182	m_imageList.Create( 16, 16, ILC_MASK | ILC_COLOR16, 2, 0);
183
184	bitmap.Attach( ::LoadBitmap( GetNonLocalizedResources(), MAKEINTRESOURCE( IDB_LOGO ) ) );
185	m_imageList.Add( &bitmap, (CBitmap*) NULL );
186	bitmap.Detach();
187
188	mTree.SetImageList(&m_imageList, TVSIL_NORMAL);
189
190exit:
191
192	if ( module )
193	{
194		FreeLibrary( module );
195		module = NULL;
196	}
197
198	// Cannot talk to the mDNSResponder service. Show the error message and exit (with kNoErr so they can see it).
199	if ( err )
200	{
201		if ( err == kDNSServiceErr_Firewall )
202		{
203			s.LoadString( IDS_FIREWALL );
204		}
205		else
206		{
207			s.LoadString( IDS_MDNSRESPONDER_NOT_AVAILABLE );
208		}
209
210		mTree.DeleteAllItems();
211		mTree.InsertItem( s, 0, 0, TVI_ROOT, TVI_LAST );
212
213		err = kNoErr;
214	}
215
216	return( err );
217}
218
219//===========================================================================================================================
220//	OnDestroy
221//===========================================================================================================================
222
223void	ExplorerBarWindow::OnDestroy( void )
224{
225	// Stop any resolves that may still be pending (shouldn't be any).
226
227	StopResolve();
228
229	// Clean up the extant browses
230	while (m_serviceRefs.size() > 0)
231	{
232		//
233		// take the head of the list
234		//
235		DNSServiceRef ref = m_serviceRefs.front();
236
237		//
238		// Stop will remove it from the list
239		//
240		Stop( ref );
241	}
242
243	// Clean up the service handlers.
244
245	int		i;
246	int		n;
247
248	n = (int) mServiceHandlers.GetSize();
249	for( i = 0; i < n; ++i )
250	{
251		delete mServiceHandlers[ i ];
252	}
253
254	CWnd::OnDestroy();
255}
256
257//===========================================================================================================================
258//	OnSize
259//===========================================================================================================================
260
261void	ExplorerBarWindow::OnSize( UINT inType, int inX, int inY )
262{
263	CWnd::OnSize( inType, inX, inY );
264	mTree.MoveWindow( 0, 0, inX, inY );
265}
266
267//===========================================================================================================================
268//	OnDoubleClick
269//===========================================================================================================================
270
271void	ExplorerBarWindow::OnDoubleClick( NMHDR *inNMHDR, LRESULT *outResult )
272{
273	HTREEITEM			item;
274	ServiceInfo *		service;
275	OSStatus			err;
276
277	DEBUG_UNUSED( inNMHDR );
278
279	item = mTree.GetSelectedItem();
280	require( item, exit );
281
282	// Tell Internet Explorer to go to the URL if it's about item
283
284	if ( item == m_about )
285	{
286		CString url;
287
288		check( mOwner );
289
290		url.LoadString( IDS_ABOUT_URL );
291		mOwner->GoToURL( url );
292	}
293	else
294	{
295		service = reinterpret_cast < ServiceInfo * > ( mTree.GetItemData( item ) );
296		require_quiet( service, exit );
297
298		err = StartResolve( service );
299		require_noerr( err, exit );
300	}
301
302exit:
303	*outResult = 0;
304}
305
306
307//===========================================================================================================================
308//	OnServiceEvent
309//===========================================================================================================================
310
311LRESULT
312ExplorerBarWindow::OnServiceEvent(WPARAM inWParam, LPARAM inLParam)
313{
314	if (WSAGETSELECTERROR(inLParam) && !(HIWORD(inLParam)))
315    {
316		dlog( kDebugLevelError, "OnServiceEvent: window error\n" );
317    }
318    else
319    {
320		SOCKET sock = (SOCKET) inWParam;
321
322		// iterate thru list
323		ServiceRefList::iterator it;
324
325		for (it = m_serviceRefs.begin(); it != m_serviceRefs.end(); it++)
326		{
327			DNSServiceRef ref = *it;
328
329			check(ref != NULL);
330
331			if ((SOCKET) DNSServiceRefSockFD(ref) == sock)
332			{
333				DNSServiceErrorType err;
334
335				err = DNSServiceProcessResult(ref);
336
337				if (err != 0)
338				{
339					CString s;
340
341					s.LoadString( IDS_MDNSRESPONDER_NOT_AVAILABLE );
342					mTree.DeleteAllItems();
343					mTree.InsertItem( s, 0, 0, TVI_ROOT, TVI_LAST );
344
345					Stop(ref);
346				}
347
348				break;
349			}
350		}
351	}
352
353	return ( 0 );
354}
355
356#if 0
357#pragma mark -
358#endif
359
360//===========================================================================================================================
361//	BrowseCallBack
362//===========================================================================================================================
363
364void DNSSD_API
365	ExplorerBarWindow::BrowseCallBack(
366		DNSServiceRef 			inRef,
367		DNSServiceFlags 		inFlags,
368		uint32_t 				inInterfaceIndex,
369		DNSServiceErrorType 	inErrorCode,
370		const char *			inName,
371		const char *			inType,
372		const char *			inDomain,
373		void *					inContext )
374{
375	ServiceHandlerEntry *		obj;
376	ServiceInfo *				service;
377	OSStatus					err;
378
379	DEBUG_UNUSED( inRef );
380
381	obj		=	NULL;
382	service = NULL;
383
384	require_noerr( inErrorCode, exit );
385	obj = reinterpret_cast < ServiceHandlerEntry * > ( inContext );
386	check( obj );
387	check( obj->obj );
388
389	//
390	// set the UI to hold off on updates
391	//
392	obj->obj->mTree.SetRedraw(FALSE);
393
394	try
395	{
396		service = new ServiceInfo;
397		require_action( service, exit, err = kNoMemoryErr );
398
399		err = UTF8StringToStringObject( inName, service->displayName );
400		check_noerr( err );
401
402		service->name = _strdup( inName );
403		require_action( service->name, exit, err = kNoMemoryErr );
404
405		service->type = _strdup( inType );
406		require_action( service->type, exit, err = kNoMemoryErr );
407
408		service->domain = _strdup( inDomain );
409		require_action( service->domain, exit, err = kNoMemoryErr );
410
411		service->ifi 		= inInterfaceIndex;
412		service->handler	= obj;
413
414		service->refs		= 1;
415
416		if (inFlags & kDNSServiceFlagsAdd) obj->obj->OnServiceAdd   (service);
417		else                               obj->obj->OnServiceRemove(service);
418
419		service = NULL;
420	}
421	catch( ... )
422	{
423		dlog( kDebugLevelError, "BrowseCallBack: exception thrown\n" );
424	}
425
426exit:
427	//
428	// If no more coming, then update UI
429	//
430	if (obj && obj->obj && ((inFlags & kDNSServiceFlagsMoreComing) == 0))
431	{
432		obj->obj->mTree.SetRedraw(TRUE);
433		obj->obj->mTree.Invalidate();
434	}
435
436	if( service )
437	{
438		delete service;
439	}
440}
441
442//===========================================================================================================================
443//	OnServiceAdd
444//===========================================================================================================================
445
446LONG	ExplorerBarWindow::OnServiceAdd( ServiceInfo * service )
447{
448	ServiceHandlerEntry *		handler;
449	int							cmp;
450	int							index;
451
452
453	check( service );
454	handler = service->handler;
455	check( handler );
456
457	cmp = FindServiceArrayIndex( handler->array, *service, index );
458	if( cmp == 0 )
459	{
460		// Found a match so update the item. The index is index + 1 so subtract 1.
461
462		index -= 1;
463		check( index < handler->array.GetSize() );
464
465		handler->array[ index ]->refs++;
466
467		delete service;
468	}
469	else
470	{
471		HTREEITEM		afterItem;
472
473		// Insert the new item in sorted order.
474
475		afterItem = ( index > 0 ) ? handler->array[ index - 1 ]->item : m_about;
476		handler->array.InsertAt( index, service );
477		service->item = mTree.InsertItem( service->displayName, 0, 0, NULL, afterItem );
478		mTree.SetItemData( service->item, (DWORD_PTR) service );
479	}
480	return( 0 );
481}
482
483//===========================================================================================================================
484//	OnServiceRemove
485//===========================================================================================================================
486
487LONG	ExplorerBarWindow::OnServiceRemove( ServiceInfo * service )
488{
489	ServiceHandlerEntry *		handler;
490	int							cmp;
491	int							index;
492
493
494	check( service );
495	handler = service->handler;
496	check( handler );
497
498	// Search to see if we know about this service instance. If so, remove it from the list.
499
500	cmp = FindServiceArrayIndex( handler->array, *service, index );
501	check( cmp == 0 );
502
503	if( cmp == 0 )
504	{
505		// Possibly found a match remove the item. The index
506		// is index + 1 so subtract 1.
507		index -= 1;
508		check( index < handler->array.GetSize() );
509
510		if ( --handler->array[ index ]->refs == 0 )
511		{
512			mTree.DeleteItem( handler->array[ index ]->item );
513			delete handler->array[ index ];
514			handler->array.RemoveAt( index );
515		}
516	}
517
518	delete service;
519	return( 0 );
520}
521
522#if 0
523#pragma mark -
524#endif
525
526//===========================================================================================================================
527//	StartResolve
528//===========================================================================================================================
529
530OSStatus	ExplorerBarWindow::StartResolve( ServiceInfo *inService )
531{
532	OSStatus		err;
533
534	check( inService );
535
536	// Stop any current resolve that may be in progress.
537
538	StopResolve();
539
540	// Resolve the service.
541	err = DNSServiceResolve( &mResolveServiceRef, 0, 0,
542		inService->name, inService->type, inService->domain, (DNSServiceResolveReply) ResolveCallBack, inService->handler );
543	require_noerr( err, exit );
544
545	err = WSAAsyncSelect((SOCKET) DNSServiceRefSockFD(mResolveServiceRef), m_hWnd, WM_PRIVATE_SERVICE_EVENT, FD_READ|FD_CLOSE);
546	require_noerr( err, exit );
547
548	m_serviceRefs.push_back(mResolveServiceRef);
549
550exit:
551	return( err );
552}
553
554//===========================================================================================================================
555//	StopResolve
556//===========================================================================================================================
557
558void	ExplorerBarWindow::StopResolve( void )
559{
560	if( mResolveServiceRef )
561	{
562		Stop( mResolveServiceRef );
563		mResolveServiceRef = NULL;
564	}
565}
566
567//===========================================================================================================================
568//	ResolveCallBack
569//===========================================================================================================================
570
571void DNSSD_API
572	ExplorerBarWindow::ResolveCallBack(
573		DNSServiceRef			inRef,
574		DNSServiceFlags			inFlags,
575		uint32_t				inInterfaceIndex,
576		DNSServiceErrorType		inErrorCode,
577		const char *			inFullName,
578		const char *			inHostName,
579		uint16_t 				inPort,
580		uint16_t 				inTXTSize,
581		const char *			inTXT,
582		void *					inContext )
583{
584	ExplorerBarWindow *			obj;
585	ServiceHandlerEntry *		handler;
586	OSStatus					err;
587
588	DEBUG_UNUSED( inRef );
589	DEBUG_UNUSED( inFlags );
590	DEBUG_UNUSED( inErrorCode );
591	DEBUG_UNUSED( inFullName );
592
593	require_noerr( inErrorCode, exit );
594	handler = (ServiceHandlerEntry *) inContext;
595	check( handler );
596	obj = handler->obj;
597	check( obj );
598
599	try
600	{
601		ResolveInfo *		resolve;
602		int					idx;
603
604		dlog( kDebugLevelNotice, "resolved %s on ifi %d to %s\n", inFullName, inInterfaceIndex, inHostName );
605
606		// Stop resolving after the first good result.
607
608		obj->StopResolve();
609
610		// Post a message to the main thread so it can handle it since MFC is not thread safe.
611
612		resolve = new ResolveInfo;
613		require_action( resolve, exit, err = kNoMemoryErr );
614
615		UTF8StringToStringObject( inHostName, resolve->host );
616
617		// rdar://problem/3841564
618		//
619		// strip trailing dot from hostname because some flavors of Windows
620		// have trouble parsing it.
621
622		idx = resolve->host.ReverseFind('.');
623
624		if ((idx > 1) && ((resolve->host.GetLength() - 1) == idx))
625		{
626			resolve->host.Delete(idx, 1);
627		}
628
629		resolve->port		= ntohs( inPort );
630		resolve->ifi		= inInterfaceIndex;
631		resolve->handler	= handler;
632
633		err = resolve->txt.SetData( inTXT, inTXTSize );
634		check_noerr( err );
635
636		obj->OnResolve(resolve);
637	}
638	catch( ... )
639	{
640		dlog( kDebugLevelError, "ResolveCallBack: exception thrown\n" );
641	}
642
643exit:
644	return;
645}
646
647//===========================================================================================================================
648//	OnResolve
649//===========================================================================================================================
650
651LONG	ExplorerBarWindow::OnResolve( ResolveInfo * resolve )
652{
653	CString				url;
654	uint8_t *			path;
655	uint8_t				pathSize;
656	char *				pathPrefix;
657	CString				username;
658	CString				password;
659
660
661	check( resolve );
662
663	// Get login info if needed.
664
665	if( resolve->handler->needsLogin )
666	{
667		LoginDialog		dialog;
668
669		if( !dialog.GetLogin( username, password ) )
670		{
671			goto exit;
672		}
673	}
674
675	// If the HTTP TXT record is a "path=" entry, use it as the resource path. Otherwise, use "/".
676
677	pathPrefix = "";
678	if( strcmp( resolve->handler->type, "_http._tcp" ) == 0 )
679	{
680		uint8_t	*	txtData;
681		uint16_t	txtLen;
682
683		resolve->txt.GetData( &txtData, &txtLen );
684
685		path	 = (uint8_t*) TXTRecordGetValuePtr(txtLen, txtData, kTXTRecordKeyPath, &pathSize);
686
687		if (path == NULL)
688		{
689			path = (uint8_t*) "";
690			pathSize = 1;
691		}
692	}
693	else
694	{
695		path		= (uint8_t *) "";
696		pathSize	= 1;
697	}
698
699	// Build the URL in the following format:
700	//
701	// <urlScheme>[<username>[:<password>]@]<name/ip>[<path>]
702
703	url.AppendFormat( TEXT( "%S" ), resolve->handler->urlScheme );					// URL Scheme
704	if( username.GetLength() > 0 )
705	{
706		url.AppendFormat( TEXT( "%s" ), username );									// Username
707		if( password.GetLength() > 0 )
708		{
709			url.AppendFormat( TEXT( ":%s" ), password );							// Password
710		}
711		url.AppendFormat( TEXT( "@" ) );
712	}
713
714	url += resolve->host;															// Host
715	url.AppendFormat( TEXT( ":%d" ), resolve->port );								// :Port
716	url.AppendFormat( TEXT( "%S" ), pathPrefix );									// Path Prefix ("/" or empty).
717	url.AppendFormat( TEXT( "%.*S" ), (int) pathSize, (char *) path );				// Path (possibly empty).
718
719	// Tell Internet Explorer to go to the URL.
720
721	check( mOwner );
722	mOwner->GoToURL( url );
723
724exit:
725	delete resolve;
726	return( 0 );
727}
728
729//===========================================================================================================================
730//	Stop
731//===========================================================================================================================
732void ExplorerBarWindow::Stop( DNSServiceRef ref )
733{
734	m_serviceRefs.remove( ref );
735
736	WSAAsyncSelect(DNSServiceRefSockFD( ref ), m_hWnd, WM_PRIVATE_SERVICE_EVENT, 0);
737
738	DNSServiceRefDeallocate( ref );
739}
740
741
742#if 0
743#pragma mark -
744#endif
745
746//===========================================================================================================================
747//	FindServiceArrayIndex
748//===========================================================================================================================
749
750DEBUG_LOCAL int	FindServiceArrayIndex( const ServiceInfoArray &inArray, const ServiceInfo &inService, int &outIndex )
751{
752	int		result;
753	int		lo;
754	int		hi;
755	int		mid;
756
757	result 	= -1;
758	mid		= 0;
759	lo 		= 0;
760	hi 		= (int)( inArray.GetSize() - 1 );
761	while( lo <= hi )
762	{
763		mid = ( lo + hi ) / 2;
764		result = inService.displayName.CompareNoCase( inArray[ mid ]->displayName );
765#if 0
766		if( result == 0 )
767		{
768			result = ( (int) inService.ifi ) - ( (int) inArray[ mid ]->ifi );
769		}
770#endif
771		if( result == 0 )
772		{
773			break;
774		}
775		else if( result < 0 )
776		{
777			hi = mid - 1;
778		}
779		else
780		{
781			lo = mid + 1;
782		}
783	}
784	if( result == 0 )
785	{
786		mid += 1;	// Bump index so new item is inserted after matching item.
787	}
788	else if( result > 0 )
789	{
790		mid += 1;
791	}
792	outIndex = mid;
793	return( result );
794}
795