1 /*
2  * Created on 14-Jun-2004
3  * Created by Paul Gardner
4  * Copyright (C) Azureus Software, Inc, All Rights Reserved.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17  *
18  */
19 
20 package com.aelitis.net.upnp.impl;
21 
22 /**
23  * @author parg
24  *
25  */
26 
27 import java.util.*;
28 import java.net.*;
29 import java.io.*;
30 
31 import com.aelitis.azureus.core.proxy.AEProxySelectorFactory;
32 
33 import org.gudy.azureus2.core3.util.AEMonitor;
34 import org.gudy.azureus2.core3.util.AERunnable;
35 import org.gudy.azureus2.core3.util.AsyncDispatcher;
36 import org.gudy.azureus2.core3.util.Debug;
37 import org.gudy.azureus2.core3.util.FileUtil;
38 import org.gudy.azureus2.core3.util.SystemTime;
39 import org.gudy.azureus2.core3.util.ThreadPool;
40 import org.gudy.azureus2.core3.util.TorrentUtils;
41 import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader;
42 import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderAdapter;
43 import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderException;
44 import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderFactory;
45 import org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocument;
46 import org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentException;
47 
48 import com.aelitis.azureus.core.util.HTTPUtils;
49 import com.aelitis.azureus.core.util.NetUtils;
50 import com.aelitis.net.upnp.*;
51 import com.aelitis.net.upnp.impl.device.*;
52 
53 public class
54 UPnPImpl
55 	extends 	ResourceDownloaderAdapter
56 	implements 	UPnP, SSDPIGDListener
57 {
58 	public static final String	NL	= "\r\n";
59 
60 	private static UPnPImpl	singleton;
61 	private static AEMonitor	class_mon 	= new AEMonitor( "UPnP:class" );
62 
63 	public static UPnP
getSingleton( UPnPAdapter adapter, String[] selected_interfaces )64 	getSingleton(
65 		UPnPAdapter		adapter,
66 		String[]		selected_interfaces )
67 
68 		throws UPnPException
69 	{
70 		try{
71 			class_mon.enter();
72 
73 			if ( singleton == null ){
74 
75 				singleton = new UPnPImpl( adapter, selected_interfaces );
76 			}
77 
78 			return( singleton );
79 
80 		}finally{
81 
82 			class_mon.exit();
83 		}
84 	}
85 
86 	private UPnPAdapter				adapter;
87 	private SSDPIGD					ssdp;
88 
89 	private Map<String,UPnPRootDeviceImpl>			root_locations	= new HashMap<String, UPnPRootDeviceImpl>();
90 
91 	private List		log_listeners		= new ArrayList();
92 	private List		log_history			= new ArrayList();
93 	private List		log_alert_history	= new ArrayList();
94 
95 	private List<UPnPListener>	rd_listeners		= new ArrayList<UPnPListener>();
96 	private AEMonitor			rd_listeners_mon 	= new AEMonitor( "UPnP:L" );
97 
98 	private int		http_calls_ok	= 0;
99 	private int		direct_calls_ok	= 0;
100 
101 	private int		trace_index		= 0;
102 
103 	private AsyncDispatcher		async_dispatcher = new AsyncDispatcher();
104 
105 	private ThreadPool	device_dispatcher	 = new ThreadPool("UPnPDispatcher", 1, true );
106 	private Set			device_dispatcher_pending	= new HashSet();
107 
108 	private Map<String,long[]>	failed_urls = new HashMap<String,long[]>();
109 
110 	protected AEMonitor	this_mon 	= new AEMonitor( "UPnP" );
111 
112 	protected
UPnPImpl( UPnPAdapter _adapter, String[] _selected_interfaces )113 	UPnPImpl(
114 		UPnPAdapter		_adapter,
115 		String[]		_selected_interfaces )
116 
117 		throws UPnPException
118 	{
119 		adapter	= _adapter;
120 
121 		ssdp = SSDPIGDFactory.create( this, _selected_interfaces );
122 
123 		ssdp.addListener(this);
124 
125 		ssdp.start();
126 	}
127 
128 	public UPnPSSDP
getSSDP()129 	getSSDP()
130 	{
131 		return( ssdp.getSSDP());
132 	}
133 
134 	public void
injectDiscoveryCache( Map cache )135 	injectDiscoveryCache(
136 		Map 		cache )
137 	{
138 		try{
139 			String	ni_s	= new String((byte[])cache.get( "ni" ), "UTF-8" );
140 			String	la_s 	= new String((byte[])cache.get( "la" ), "UTF-8" );
141 			String	usn 	= new String((byte[])cache.get( "usn" ), "UTF-8" );
142 			String	loc_s 	= new String((byte[])cache.get( "loc" ), "UTF-8" );
143 
144 			NetworkInterface	network_interface = NetUtils.getByName( ni_s );
145 
146 			if ( network_interface == null ){
147 
148 				return;
149 			}
150 
151 			InetAddress	local_address = InetAddress.getByName( la_s );
152 
153 			URL location = new URL( loc_s );
154 
155 			rootDiscovered( network_interface, local_address, usn, location );
156 
157 		}catch( Throwable e ){
158 
159 			Debug.out( e );
160 		}
161 	}
162 
163 	public void
rootDiscovered( final NetworkInterface network_interface, final InetAddress local_address, final String usn, final URL location )164 	rootDiscovered(
165 		final NetworkInterface		network_interface,
166 		final InetAddress			local_address,
167 		final String				usn,
168 		final URL					location )
169 	{
170 
171 			// we need to take this operation off the main thread as it can take some time. This is a single
172 			// concurrency queued thread pool so things get done serially in the right order
173 
174 		try{
175 			rd_listeners_mon.enter();
176 
177 			if ( device_dispatcher_pending.contains( usn )){
178 
179 				// System.out.println( "UPnP: skipping discovery of " + usn + " as already pending (queue=" + device_dispatcher_pending.size() + ")" );
180 
181 				return;
182 			}
183 
184 			if ( device_dispatcher_pending.size() > 512 ){
185 
186 				Debug.out( "Device dispatcher queue is full - dropping discovery of " + usn + "/" + location );
187 			}
188 
189 			device_dispatcher_pending.add( usn );
190 
191 		}finally{
192 
193 			rd_listeners_mon.exit();
194 		}
195 
196 		device_dispatcher.run(
197 			new AERunnable()
198 			{
199 				public void
200 				runSupport()
201 				{
202 					final UPnPRootDeviceImpl old_root_device;
203 
204 					try{
205 						rd_listeners_mon.enter();
206 
207 						old_root_device = (UPnPRootDeviceImpl)root_locations.get( usn );
208 
209 						device_dispatcher_pending.remove( usn );
210 
211 					}finally{
212 
213 						rd_listeners_mon.exit();
214 					}
215 
216 					if ( old_root_device != null ){
217 
218 							// we remember one route to the device - if the network interfaces change
219 							// we do a full reset so we don't need to deal with that here
220 
221 						if ( !old_root_device.getNetworkInterface().getName().equals( network_interface.getName())){
222 
223 							if ( old_root_device.addAlternativeLocation( location )){
224 
225 								log( "Adding alternative location " +location + " to " + usn );
226 							}
227 
228 							return;
229 						}
230 
231 							// check that the device's location is the same
232 
233 						if ( old_root_device.getLocation().equals( location )){
234 
235 							return;
236 						}
237 					}
238 
239 					if ( old_root_device != null ){
240 
241 							// something changed, resetablish everything
242 
243 						try{
244 								// not the best "atomic" code here but it'll do as the code that adds roots (this)
245 								// is single threaded via the dispatcher
246 
247 							rd_listeners_mon.enter();
248 
249 							root_locations.remove( usn );
250 
251 						}finally{
252 
253 							rd_listeners_mon.exit();
254 						}
255 
256 						old_root_device.destroy( true );
257 					}
258 
259 					List	listeners;
260 
261 					try{
262 						rd_listeners_mon.enter();
263 
264 						listeners = new ArrayList( rd_listeners );
265 
266 					}finally{
267 
268 						rd_listeners_mon.exit();
269 					}
270 
271 					for (int i=0;i<listeners.size();i++){
272 
273 						try{
274 							if ( !((UPnPListener)listeners.get(i)).deviceDiscovered( usn, location )){
275 
276 								return;
277 							}
278 
279 						}catch( Throwable e ){
280 
281 							Debug.printStackTrace(e);
282 						}
283 					}
284 
285 					log( "UPnP: root discovered: usn=" + usn + ", location=" + location + ", ni=" + network_interface.getName() + ",local=" + local_address.toString() );
286 
287 					try{
288 						UPnPRootDeviceImpl new_root_device = new UPnPRootDeviceImpl( UPnPImpl.this, network_interface, local_address, usn, location );
289 
290 						try{
291 							rd_listeners_mon.enter();
292 
293 							root_locations.put( usn, new_root_device );
294 
295 							listeners = new ArrayList( rd_listeners );
296 
297 						}finally{
298 
299 							rd_listeners_mon.exit();
300 						}
301 
302 						for (int i=0;i<listeners.size();i++){
303 
304 							try{
305 								((UPnPListener)listeners.get(i)).rootDeviceFound( new_root_device );
306 
307 							}catch( Throwable e ){
308 
309 								Debug.printStackTrace(e);
310 							}
311 						}
312 
313 					}catch( UPnPException e ){
314 
315 						String	message = e.getMessage();
316 
317 						String msg = message==null?Debug.getNestedExceptionMessageAndStack( e ):message;
318 
319 						adapter.log( msg );
320 					}
321 				}
322 			});
323 	}
324 
325 	public void
rootAlive( String usn, URL location )326 	rootAlive(
327 		String		usn,
328 		URL			location )
329 	{
330 		UPnPRootDeviceImpl root_device = (UPnPRootDeviceImpl)root_locations.get( usn );
331 
332 		if ( root_device == null ){
333 
334 			ssdp.searchNow();
335 		}
336 	}
337 
338 	public void
rootLost( final InetAddress local_address, final String usn )339 	rootLost(
340 		final InetAddress	local_address,
341 		final String		usn )
342 	{
343 			// we need to take this operation off the main thread as it can take some time
344 
345 		device_dispatcher.run(
346 			new AERunnable()
347 			{
348 				public void
349 				runSupport()
350 				{
351 					UPnPRootDeviceImpl	root_device	= null;
352 
353 					try{
354 						rd_listeners_mon.enter();
355 
356 						root_device = (UPnPRootDeviceImpl)root_locations.remove( usn );
357 
358 					}finally{
359 
360 						rd_listeners_mon.exit();
361 					}
362 
363 					if ( root_device == null ){
364 
365 						return;
366 					}
367 
368 					log( "UPnP: root lost: usn=" + usn + ", location=" + root_device.getLocation() + ", ni=" + root_device.getNetworkInterface().getName() + ",local=" + root_device.getLocalAddress().toString());
369 
370 					root_device.destroy( false );
371 				}
372 			});
373 	}
374 
375 	public void
interfaceChanged( NetworkInterface network_interface )376 	interfaceChanged(
377 		NetworkInterface	network_interface )
378 	{
379 		reset();
380 	}
381 
382 	public void
search()383 	search()
384 	{
385 		ssdp.searchNow();
386 	}
387 
388 	public void
search( String[] STs )389 	search(
390 		String[]	STs )
391 	{
392 		ssdp.searchNow( STs );
393 	}
394 
395 	public void
reset()396 	reset()
397 	{
398 		log( "UPnP: reset" );
399 
400 		List	roots;
401 
402 		try{
403 			rd_listeners_mon.enter();
404 
405 			roots = new ArrayList(root_locations.values());
406 
407 			root_locations.clear();
408 
409 		}finally{
410 
411 			rd_listeners_mon.exit();
412 		}
413 
414 		for (int i=0;i<roots.size();i++){
415 
416 			((UPnPRootDeviceImpl)roots.get(i)).destroy( true );
417 		}
418 
419 		ssdp.searchNow();
420 	}
421 
422 	public SimpleXMLParserDocument
parseXML( InputStream _is )423 	parseXML(
424 		InputStream		_is )
425 
426 		throws SimpleXMLParserDocumentException, IOException
427 	{
428 			// ASSUME UTF-8
429 
430 		ByteArrayOutputStream		baos = null;
431 
432 		try{
433 			baos = new ByteArrayOutputStream(1024);
434 
435 			byte[]	buffer = new byte[8192];
436 
437 			while(true){
438 
439 				int	len = _is.read( buffer );
440 
441 				if ( len <= 0 ){
442 
443 					break;
444 				}
445 
446 				baos.write( buffer, 0, len );
447 			}
448 		}finally{
449 
450 			baos.close();
451 		}
452 
453 		byte[]	bytes_in = baos.toByteArray();
454 
455 		InputStream	is = new ByteArrayInputStream( bytes_in );
456 
457 			// Gudy's router was returning trailing nulls which then stuffed up the
458 			// XML parser. Hence this code to try and strip them
459 
460 		try{
461 			StringBuffer	data = new StringBuffer(1024);
462 
463 			LineNumberReader	lnr = new LineNumberReader( new InputStreamReader( is, "UTF-8" ));
464 
465 			Set	ignore_map = null;
466 
467 			while( true ){
468 
469 				String	line = lnr.readLine();
470 
471 				if ( line == null ){
472 
473 					break;
474 				}
475 
476 					// remove any obviously invalid characters - I've seen some routers generate stuff like
477 					// 0x18 which stuffs the xml parser with "invalid unicode character"
478 
479 				for (int i=0;i<line.length();i++){
480 
481 					char	c = line.charAt(i);
482 
483 					if ( c < 0x20 && c != '\r' && c != '\t' ){
484 
485 						data.append( ' ' );
486 
487 						if ( ignore_map == null ){
488 
489 							ignore_map = new HashSet();
490 						}
491 
492 						Character	cha = new Character(c);
493 
494 						if ( !ignore_map.contains( cha )){
495 
496 							ignore_map.add( cha );
497 
498 							adapter.trace( "    ignoring character(s) " + (int)c + " in xml response" );
499 						}
500 					}else{
501 
502 						data.append( c );
503 					}
504 				}
505 
506 				data.append( "\n" );
507 			}
508 
509 			String	data_str = data.toString();
510 
511 			adapter.trace( "UPnP:Response:" + data_str );
512 
513 			try{
514 				SimpleXMLParserDocument doc = adapter.parseXML( data_str );
515 
516 				return( doc );
517 
518 			}catch( Throwable e ){
519 
520 					// try some hacks for known errors
521 
522 				if ( data_str.contains("<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">")){
523 
524 					data_str = data_str.replace("<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">", "<scpd>");
525 
526 					return( adapter.parseXML( data_str ));
527 				}
528 
529 				throw( e );
530 			}
531 		}catch( Throwable e ){
532 
533 			try{
534 				FileOutputStream	trace = new FileOutputStream( getTraceFile());
535 
536 				try{
537 					trace.write( bytes_in );
538 
539 				}finally{
540 
541 					trace.close();
542 				}
543 			}catch( Throwable f ){
544 
545 				adapter.log(f);
546 			}
547 
548 			if ( e instanceof SimpleXMLParserDocumentException ){
549 
550 				throw((SimpleXMLParserDocumentException)e);
551 			}
552 
553 			throw( new SimpleXMLParserDocumentException(e ));
554 		}
555 	}
556 
557 	public SimpleXMLParserDocument
downloadXML( UPnPRootDeviceImpl root, URL url )558 	downloadXML(
559 		UPnPRootDeviceImpl	root,
560 		URL					url )
561 
562 		throws UPnPException
563 	{
564 		return( downloadXMLSupport( null, url ));
565 	}
566 
567 	public SimpleXMLParserDocument
downloadXML( UPnPDeviceImpl device, URL url )568 	downloadXML(
569 		UPnPDeviceImpl	device,
570 		URL				url )
571 
572 		throws UPnPException
573 	{
574 		try{
575 				// some devices have borked relative urls, work around
576 
577 			device.restoreRelativeBaseURL();
578 
579 			return( downloadXMLSupport( device.getFriendlyName(), url ));
580 
581 		}catch( UPnPException e ){
582 
583 			device.clearRelativeBaseURL();
584 
585 			return( downloadXMLSupport( device.getFriendlyName(), url ));
586 		}
587 	}
588 
589 	protected SimpleXMLParserDocument
downloadXMLSupport( String friendly_name, URL url )590 	downloadXMLSupport(
591 		String			friendly_name,
592 		URL				url )
593 
594 		throws UPnPException
595 	{
596 		String url_str = url.toExternalForm();
597 
598 		boolean	record_failure = true;
599 
600 		try{
601 			TorrentUtils.setTLSDescription( "UPnP Device" + ( friendly_name==null?"":( ": " + friendly_name )));
602 
603 			ResourceDownloaderFactory rdf = adapter.getResourceDownloaderFactory();
604 
605 			int	retries;
606 
607 			synchronized( failed_urls ){
608 
609 				long[] fails = failed_urls.get( url_str );
610 
611 				if ( fails == null ){
612 
613 					retries = 3;
614 
615 				}else{
616 
617 					long	consec_fails 	= fails[0];
618 					long	last_fail		= fails[1];
619 
620 					long	max_period	= 10*60*1000;
621 					long	period 		= 60*1000;
622 
623 					for (int i=0;i<consec_fails;i++){
624 
625 						period <<= 1;
626 
627 						if ( period >= max_period ){
628 
629 							period = max_period;
630 
631 							break;
632 						}
633 					}
634 
635 					if ( SystemTime.getMonotonousTime() - last_fail < period ){
636 
637 						record_failure = false;
638 
639 						throw( new UPnPException( "Download failed too recently, ignoring" ));
640 					}
641 
642 					retries = 1;
643 				}
644 			}
645 
646 			ResourceDownloader rd = rdf.getRetryDownloader( rdf.create( url, true ), retries );
647 
648 			rd.addListener( this );
649 
650 			InputStream	data = rd.download();
651 
652 			try{
653 
654 				SimpleXMLParserDocument res = parseXML( data );
655 
656 				synchronized( failed_urls ){
657 
658 					failed_urls.remove( url_str );
659 				}
660 
661 				return( res );
662 
663 			}finally{
664 
665 				data.close();
666 			}
667 		}catch( Throwable e ){
668 
669 			if ( record_failure ){
670 
671 				synchronized( failed_urls ){
672 
673 					if ( failed_urls.size() >= 64 ){
674 
675 						failed_urls.clear();
676 					}
677 
678 					long[] fails = failed_urls.get( url_str );
679 
680 					if ( fails == null ){
681 
682 						fails = new long[2];
683 
684 						failed_urls.put( url_str, fails );
685 					}
686 
687 					fails[0]++;
688 
689 					fails[1] = SystemTime.getMonotonousTime();
690 				}
691 
692 				adapter.log( "Failed to parse XML from :" + url_str + ": " + Debug.getNestedExceptionMessageAndStack(e));
693 			}
694 
695 			if (e instanceof UPnPException ){
696 
697 				throw((UPnPException)e);
698 			}
699 
700 			throw( new UPnPException( "Root device location '" + url + "' - data read failed", e ));
701 
702 		}finally{
703 
704 			TorrentUtils.setTLSDescription( null );
705 		}
706 	}
707 
708 	protected boolean
forceDirect()709 	forceDirect()
710 	{
711 		String	http_proxy 	= System.getProperty( "http.proxyHost" );
712 		String	socks_proxy = System.getProperty( "socksProxyHost" );
713 
714 			// extremely unlikely we want to proxy upnp requests
715 
716 		boolean force_direct = 	( http_proxy != null && http_proxy.trim().length() > 0 ) ||
717 								( socks_proxy != null && socks_proxy.trim().length() > 0 );
718 
719 		return( force_direct );
720 	}
721 
722 
723 
724 	public SimpleXMLParserDocument
performSOAPRequest( UPnPService service, String soap_action, String request )725 	performSOAPRequest(
726 		UPnPService		service,
727 		String			soap_action,
728 		String			request )
729 
730 		throws SimpleXMLParserDocumentException, UPnPException, IOException
731 	{
732 		SimpleXMLParserDocument	res;
733 
734 		if ( service.getDirectInvocations() || forceDirect()){
735 
736 			res = performSOAPRequest( service, soap_action, request, false );
737 
738 		}else{
739 
740 			try{
741 				res =  performSOAPRequest( service, soap_action, request, true );
742 
743 				http_calls_ok++;
744 
745 			}catch( IOException e ){
746 
747 				res = performSOAPRequest( service, soap_action, request, false );
748 
749 				direct_calls_ok++;
750 
751 				if ( direct_calls_ok == 1 ){
752 
753 					log( "Invocation via http connection failed (" + e.getMessage() + ") but socket connection succeeded" );
754 				}
755 			}
756 		}
757 
758 		return( res );
759 	}
760 
761 	/**
762 	 * The use_http_connection flag is set to false sometimes to avoid using
763 	 * the URLConnection library for some dopey UPnP routers.
764 	 */
765 	public SimpleXMLParserDocument
performSOAPRequest( UPnPService service, String soap_action, String request, boolean use_http_connection)766 	performSOAPRequest(
767 		UPnPService		service,
768 		String			soap_action,
769 		String			request,
770 		boolean			use_http_connection)
771 
772 		throws SimpleXMLParserDocumentException, UPnPException, IOException
773 	{
774 		//long	start = SystemTime.getMonotonousTime();
775 
776 		List<URL>	controls = service.getControlURLs();
777 
778 		Throwable last_error = null;
779 
780 		for ( URL control: controls ){
781 
782 			boolean	good_url = true;
783 
784 			try{
785 				adapter.trace( "UPnP:Request: -> " + control + "," + request );
786 
787 				if ( use_http_connection ){
788 
789 					try{
790 						AEProxySelectorFactory.getSelector().startNoProxy();
791 
792 						TorrentUtils.setTLSDescription( "UPnP Device: " + service.getDevice().getFriendlyName());
793 
794 						HttpURLConnection	con1 = (HttpURLConnection)control.openConnection();
795 
796 						con1.setRequestProperty( "SOAPAction", "\""+ soap_action + "\"");
797 
798 						con1.setRequestProperty( "Content-Type", "text/xml; charset=\"utf-8\"" );
799 
800 						con1.setRequestProperty( "User-Agent", "Azureus (UPnP/1.0)" );
801 
802 						con1.setRequestMethod( "POST" );
803 
804 						con1.setDoInput( true );
805 						con1.setDoOutput( true );
806 
807 						OutputStream	os = con1.getOutputStream();
808 
809 						PrintWriter	pw = new PrintWriter( new OutputStreamWriter(os, "UTF-8" ));
810 
811 						pw.println( request );
812 
813 						pw.flush();
814 
815 						con1.connect();
816 
817 						if ( con1.getResponseCode() == 405 || con1.getResponseCode() == 500 ){
818 
819 								// gotta retry with M-POST method
820 
821 							try{
822 								HttpURLConnection con2 = (HttpURLConnection)control.openConnection();
823 
824 								con2.setRequestProperty( "Content-Type", "text/xml; charset=\"utf-8\"" );
825 
826 								con2.setRequestMethod( "M-POST" );
827 
828 								con2.setRequestProperty( "MAN", "\"http://schemas.xmlsoap.org/soap/envelope/\"; ns=01" );
829 
830 								con2.setRequestProperty( "01-SOAPACTION", "\""+ soap_action + "\"");
831 
832 								con2.setDoInput( true );
833 								con2.setDoOutput( true );
834 
835 								os = con2.getOutputStream();
836 
837 								pw = new PrintWriter( new OutputStreamWriter(os, "UTF-8" ));
838 
839 								pw.println( request );
840 
841 								pw.flush();
842 
843 								con2.connect();
844 
845 								return( parseXML(con2.getInputStream()));
846 
847 							}catch( Throwable e ){
848 
849 							}
850 
851 							InputStream es = con1.getErrorStream();
852 
853 							String	info = null;
854 
855 							try{
856 								info = FileUtil.readInputStreamAsString( es, 512 );
857 
858 							}catch( Throwable e ){
859 							}
860 
861 							String error = "SOAP RPC failed: " + con1.getResponseCode() + " " + con1.getResponseMessage();
862 
863 							if ( info != null ){
864 
865 								error += " - " + info;
866 							}
867 
868 							throw( new IOException ( error ));
869 
870 						}else{
871 
872 							return( parseXML(con1.getInputStream()));
873 						}
874 					}finally{
875 
876 						TorrentUtils.setTLSDescription( null );
877 
878 						AEProxySelectorFactory.getSelector().endNoProxy();
879 					}
880 				}else{
881 					final int CONNECT_TIMEOUT 	= 15*1000;
882 					final int READ_TIMEOUT		= 30*1000;
883 
884 					Socket	socket = new Socket( Proxy.NO_PROXY );
885 
886 					socket.connect( new InetSocketAddress( control.getHost(), control.getPort()), CONNECT_TIMEOUT );
887 
888 					socket.setSoTimeout( READ_TIMEOUT );
889 
890 					try{
891 						PrintWriter	pw = new PrintWriter(new OutputStreamWriter( socket.getOutputStream(), "UTF8" ));
892 
893 						String	url_target = control.toString();
894 
895 						int	p1 	= url_target.indexOf( "://" ) + 3;
896 						p1		= url_target.indexOf( "/", p1 );
897 
898 						url_target = url_target.substring( p1 );
899 
900 						pw.print( "POST " + url_target + " HTTP/1.1" + NL );
901 						pw.print( "Content-Type: text/xml; charset=\"utf-8\"" + NL );
902 						pw.print( "SOAPAction: \"" + soap_action + "\"" + NL );
903 						pw.print( "User-Agent: Azureus (UPnP/1.0)" + NL );
904 						pw.print( "Host: " + control.getHost() + NL );
905 						pw.print( "Content-Length: " + request.getBytes( "UTF8" ).length + NL );
906 						pw.print( "Connection: Keep-Alive" + NL );
907 						pw.print( "Pragma: no-cache" + NL + NL );
908 
909 						pw.print( request );
910 
911 						pw.flush();
912 
913 						InputStream	is = HTTPUtils.decodeChunkedEncoding( socket, true );
914 
915 						return( parseXML( is ));
916 
917 					}finally{
918 
919 						try{
920 							socket.close();
921 
922 						}catch( Throwable e ){
923 
924 							Debug.printStackTrace(e);
925 						}
926 					}
927 				}
928 			}catch( Throwable e ){
929 
930 				last_error = e;
931 
932 				good_url = false;
933 
934 			}finally{
935 
936 				if ( good_url ){
937 
938 					service.setPreferredControlURL( control );
939 				}
940 				//System.out.println( "UPnP: invocation of " + control + "/" + soap_action + " took " + ( SystemTime.getMonotonousTime() - start ));
941 			}
942 		}
943 
944 		if ( last_error == null ){
945 
946 			throw( new UPnPException( "inconsistent!" ));
947 		}
948 
949 		if ( last_error instanceof SimpleXMLParserDocumentException ){
950 
951 			throw((SimpleXMLParserDocumentException)last_error);
952 
953 		}else if ( last_error instanceof UPnPException ){
954 
955 			throw((UPnPException)last_error);
956 
957 		}else if ( last_error instanceof IOException ){
958 
959 			throw((IOException)last_error);
960 
961 		}else{
962 
963 			throw((RuntimeException)last_error );
964 		}
965 	}
966 
967 	protected File
getTraceFile()968 	getTraceFile()
969 	{
970 		try{
971 			this_mon.enter();
972 
973 			trace_index++;
974 
975 			if ( trace_index == 6 ){
976 
977 				trace_index = 1;
978 			}
979 
980 			return( new File( adapter.getTraceDir(), "upnp_trace" + trace_index + ".log" ));
981 		}finally{
982 
983 			this_mon.exit();
984 		}
985 	}
986 
987 	public UPnPAdapter
getAdapter()988 	getAdapter()
989 	{
990 		return( adapter );
991 	}
992 
993 	public void
reportActivity( ResourceDownloader downloader, String activity )994 	reportActivity(
995 		ResourceDownloader	downloader,
996 		String				activity )
997 	{
998 		log( activity );
999 	}
1000 
1001 	public void
failed( ResourceDownloader downloader, ResourceDownloaderException e )1002 	failed(
1003 		ResourceDownloader			downloader,
1004 		ResourceDownloaderException e )
1005 	{
1006 		log( e );
1007 	}
1008 
1009 	public void
log( Throwable e )1010 	log(
1011 		Throwable e )
1012 	{
1013 		log( e.toString());
1014 	}
1015 
1016 	public void
log( String str )1017 	log(
1018 		String	str )
1019 	{
1020 		List	old_listeners;
1021 
1022 		try{
1023 			this_mon.enter();
1024 
1025 			old_listeners = new ArrayList(log_listeners);
1026 
1027 			log_history.add( str );
1028 
1029 			if ( log_history.size() > 32 ){
1030 
1031 				log_history.remove(0);
1032 			}
1033 		}finally{
1034 
1035 			this_mon.exit();
1036 		}
1037 
1038 		for (int i=0;i<old_listeners.size();i++){
1039 
1040 			((UPnPLogListener)old_listeners.get(i)).log( str );
1041 		}
1042 	}
1043 
1044 	public void
logAlert( String str, boolean error, int type )1045 	logAlert(
1046 		String	str,
1047 		boolean	error,
1048 		int		type )
1049 	{
1050 		List	old_listeners;
1051 
1052 		try{
1053 			this_mon.enter();
1054 
1055 			old_listeners = new ArrayList(log_listeners);
1056 
1057 			log_alert_history.add(new Object[]{ str, new Boolean( error ), new Integer( type )});
1058 
1059 			if ( log_alert_history.size() > 32 ){
1060 
1061 				log_alert_history.remove(0);
1062 			}
1063 		}finally{
1064 
1065 			this_mon.exit();
1066 		}
1067 
1068 		for (int i=0;i<old_listeners.size();i++){
1069 
1070 			((UPnPLogListener)old_listeners.get(i)).logAlert( str, error, type );
1071 		}
1072 	}
1073 
1074 	public void
addLogListener( UPnPLogListener l )1075 	addLogListener(
1076 		UPnPLogListener	l )
1077 	{
1078 		List	old_logs;
1079 		List	old_alerts;
1080 
1081 		try{
1082 			this_mon.enter();
1083 
1084 			old_logs 	= new ArrayList(log_history);
1085 			old_alerts 	= new ArrayList(log_alert_history);
1086 
1087 			log_listeners.add( l );
1088 		}finally{
1089 
1090 			this_mon.exit();
1091 		}
1092 
1093 		for (int i=0;i<old_logs.size();i++){
1094 
1095 			l.log((String)old_logs.get(i));
1096 		}
1097 
1098 		for (int i=0;i<old_alerts.size();i++){
1099 
1100 			Object[]	entry = (Object[])old_alerts.get(i);
1101 
1102 			l.logAlert((String)entry[0], ((Boolean)entry[1]).booleanValue(), ((Integer)entry[2]).intValue());
1103 		}
1104 	}
1105 
1106 	public void
removeLogListener( UPnPLogListener l )1107 	removeLogListener(
1108 		UPnPLogListener	l )
1109 	{
1110 		log_listeners.remove( l );
1111 	}
1112 
1113 	public UPnPRootDevice[]
getRootDevices()1114 	getRootDevices()
1115 	{
1116 		try{
1117 			this_mon.enter();
1118 
1119 			return( root_locations.values().toArray( new UPnPRootDevice[ root_locations.size()] ));
1120 
1121 		}finally{
1122 
1123 			this_mon.exit();
1124 		}
1125 	}
1126 
1127 	public void
addRootDeviceListener( final UPnPListener l )1128 	addRootDeviceListener(
1129 		final UPnPListener	l )
1130 	{
1131 		final List<UPnPRootDeviceImpl>	old_locations;
1132 
1133 		try{
1134 			this_mon.enter();
1135 
1136 			old_locations = new ArrayList<UPnPRootDeviceImpl>(root_locations.values());
1137 
1138 			rd_listeners.add( l );
1139 
1140 		}finally{
1141 
1142 			this_mon.exit();
1143 		}
1144 
1145 		if ( old_locations.size() > 0 ){
1146 
1147 				// if we have a misbehaving device (hanging on requests for example) then this can cause
1148 				// logic running on the new listener to hang the calling thread (e.g. the UPnPMediaServer)
1149 				// which can then bork its caller (e.g. PlayUtils.getMediaServerContentURL) and subsequent
1150 				// badness (UI hang). As a general fix for this go async here
1151 
1152 			async_dispatcher.dispatch(
1153 				new AERunnable()
1154 				{
1155 					@Override
1156 					public void
1157 					runSupport()
1158 					{
1159 						for (int i=0;i<old_locations.size();i++){
1160 
1161 							UPnPRootDevice	device = (UPnPRootDevice)old_locations.get(i);
1162 
1163 							try{
1164 
1165 								if ( l.deviceDiscovered( device.getUSN(), device.getLocation())){
1166 
1167 									l.rootDeviceFound(device);
1168 								}
1169 
1170 							}catch( Throwable e ){
1171 
1172 								Debug.printStackTrace(e);
1173 							}
1174 						}
1175 					}
1176 				});
1177 		}
1178 	}
1179 
1180 	public void
removeRootDeviceListener( UPnPListener l )1181 	removeRootDeviceListener(
1182 		UPnPListener	l )
1183 	{
1184 		try{
1185 			this_mon.enter();
1186 
1187 			rd_listeners.remove( l );
1188 
1189 		}finally{
1190 
1191 			this_mon.exit();
1192 		}
1193 	}
1194 
1195 	/*
1196 	public static void
1197 	main(
1198 		String[]		args )
1199 	{
1200 		try{
1201 			UPnP	upnp = UPnPFactory.getSingleton(null,null);	// won't work with null ....
1202 
1203 			upnp.addRootDeviceListener(
1204 					new UPnPListener()
1205 					{
1206 						public boolean
1207 						deviceDiscovered(
1208 							String		USN,
1209 							URL			location )
1210 						{
1211 							return( true );
1212 						}
1213 
1214 						public void
1215 						rootDeviceFound(
1216 							UPnPRootDevice		device )
1217 						{
1218 							try{
1219 								processDevice( device.getDevice() );
1220 
1221 							}catch( Throwable e ){
1222 
1223 								e.printStackTrace();
1224 							}
1225 						}
1226 					});
1227 
1228 			upnp.addLogListener(
1229 				new UPnPLogListener()
1230 				{
1231 					public void
1232 					log(
1233 						String	str )
1234 					{
1235 						System.out.println( str );
1236 					}
1237 					public void
1238 					logAlert(
1239 						String	str,
1240 						boolean	error,
1241 						int		type )
1242 					{
1243 						System.out.println( str );
1244 					}
1245 
1246 				});
1247 
1248 			Thread.sleep(20000);
1249 
1250 		}catch( Throwable e ){
1251 
1252 			e.printStackTrace();
1253 		}
1254 	}
1255 
1256 	protected static void
1257 	processDevice(
1258 		UPnPDevice	device )
1259 
1260 		throws UPnPException
1261 	{
1262 		if ( device.getDeviceType().equalsIgnoreCase("urn:schemas-upnp-org:device:WANConnectionDevice:1")){
1263 
1264 			System.out.println( "got device");
1265 
1266 			UPnPService[] services = device.getServices();
1267 
1268 			for (int i=0;i<services.length;i++){
1269 
1270 				UPnPService	s = services[i];
1271 
1272 				if ( s.getServiceType().equalsIgnoreCase( "urn:schemas-upnp-org:service:WANIPConnection:1")){
1273 
1274 					System.out.println( "got service" );
1275 
1276 					UPnPAction[]	actions = s.getActions();
1277 
1278 					for (int j=0;j<actions.length;j++){
1279 
1280 						System.out.println( actions[j].getName());
1281 					}
1282 
1283 					UPnPStateVariable[]	vars = s.getStateVariables();
1284 
1285 					for (int j=0;j<vars.length;j++){
1286 
1287 						System.out.println( vars[j].getName());
1288 					}
1289 
1290 					UPnPStateVariable noe = s.getStateVariable("PortMappingNumberOfEntries");
1291 
1292 					System.out.println( "noe = " + noe.getValue());
1293 
1294 					UPnPWANIPConnection wan_ip = (UPnPWANIPConnection)s.getSpecificService();
1295 
1296 					UPnPWANConnectionPortMapping[] ports = wan_ip.getPortMappings();
1297 
1298 					wan_ip.addPortMapping( true, 7007, "Moo!" );
1299 
1300 					UPnPAction act	= s.getAction( "GetGenericPortMappingEntry" );
1301 
1302 					UPnPActionInvocation inv = act.getInvocation();
1303 
1304 					inv.addArgument( "NewPortMappingIndex", "0" );
1305 
1306 					UPnPActionArgument[] outs = inv.invoke();
1307 
1308 					for (int j=0;j<outs.length;j++){
1309 
1310 						System.out.println( outs[j].getName() + " = " + outs[j].getValue());
1311 					}
1312 				}
1313 			}
1314 		}else{
1315 
1316 			UPnPDevice[]	kids = device.getSubDevices();
1317 
1318 			for (int i=0;i<kids.length;i++){
1319 
1320 				processDevice( kids[i] );
1321 			}
1322 		}
1323 	}
1324 	*/
1325 }
1326