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