1 /* 2 * Created on Mar 21, 2006 3:09:00 PM 3 * Copyright (C) Azureus Software, Inc, All Rights Reserved. 4 * 5 * This program is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU General Public License 7 * as published by the Free Software Foundation; either version 2 8 * of the License, or (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program; if not, write to the Free Software 15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 16 */ 17 package org.gudy.azureus2.core3.util; 18 19 import java.io.File; 20 import java.io.IOException; 21 import java.io.UnsupportedEncodingException; 22 import java.net.*; 23 import java.util.ArrayList; 24 import java.util.HashMap; 25 import java.util.HashSet; 26 import java.util.List; 27 import java.util.Locale; 28 import java.util.Map; 29 import java.util.Set; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 33 import javax.net.ssl.HttpsURLConnection; 34 import javax.net.ssl.SSLContext; 35 import javax.net.ssl.SSLException; 36 import javax.net.ssl.SSLSocket; 37 import javax.net.ssl.SSLSocketFactory; 38 import javax.net.ssl.TrustManager; 39 40 import org.gudy.azureus2.core3.config.COConfigurationManager; 41 import org.gudy.azureus2.core3.download.DownloadManager; 42 import org.gudy.azureus2.core3.security.SESecurityManager; 43 import org.gudy.azureus2.core3.torrent.TOTorrent; 44 import org.gudy.azureus2.plugins.download.Download; 45 import org.gudy.azureus2.plugins.torrent.Torrent; 46 import org.gudy.azureus2.plugins.torrent.TorrentAnnounceURLList; 47 import org.gudy.azureus2.plugins.torrent.TorrentAnnounceURLListSet; 48 import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader; 49 import org.gudy.azureus2.plugins.utils.resourceuploader.ResourceUploader; 50 import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils; 51 import org.gudy.bouncycastle.util.encoders.Base64; 52 53 import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin; 54 55 56 /** 57 * @author TuxPaper 58 * @created Mar 21, 2006 59 * 60 */ 61 public class UrlUtils 62 { 63 private static final ThreadPool connect_pool = new ThreadPool( "URLConnectWithTimeout", 8, true ); 64 65 static{ connect_pool.setWarnWhenFull()66 connect_pool.setWarnWhenFull(); 67 } 68 69 private static final String[] prefixes = new String[] { 70 "http://", 71 "https://", 72 "ftp://", 73 "dht://", 74 "magnet:?", 75 "magnet://?", 76 "maggot://" }; 77 78 private static int MAGNETURL_STARTS_AT = 3; // dht:// is a form of magnet URL 79 80 private static final Object[] XMLescapes = new Object[] { 81 new String[] { "&", "&" }, 82 new String[] { ">", ">" }, 83 new String[] { "<", "<" }, 84 new String[] { "\"", """ }, 85 new String[] { "'", "'" }, 86 }; 87 88 public static Map<String,String> decodeArgs( String args )89 decodeArgs( 90 String args ) 91 { 92 Map<String,String> result = new HashMap<String, String>(); 93 94 String[] bits = (args.startsWith("?")?args.substring(1):args).split( "&" ); 95 96 for ( String bit: bits ){ 97 98 String[] temp = bit.split( "=", 2 ); 99 100 if ( temp.length == 2 ){ 101 102 String lhs = temp[0].toLowerCase( Locale.US ); 103 104 String rhs = decode( temp[1] ); 105 106 result.put( lhs, rhs ); 107 108 }else{ 109 110 result.put( "", decode( temp[0] )); 111 } 112 } 113 114 return( result ); 115 } 116 117 public static String getMagnetURI( byte[] hash )118 getMagnetURI( 119 byte[] hash ) 120 { 121 return( "magnet:?xt=urn:btih:" + Base32.encode( hash )); 122 } 123 124 public static String getMagnetURI( byte[] hash, String name, String[] networks )125 getMagnetURI( 126 byte[] hash, 127 String name, 128 String[] networks ) 129 { 130 String magnet_uri = getMagnetURI( hash ); 131 132 magnet_uri += encodeName( name ); 133 134 magnet_uri += encodeNetworks( networks ); 135 136 return( magnet_uri ); 137 } 138 139 private static String encodeName( String name )140 encodeName( 141 String name ) 142 { 143 if ( name == null ){ 144 145 return( "" ); 146 147 }else{ 148 149 return( "&dn=" + UrlUtils.encode(name)); 150 } 151 } 152 153 private static String encodeNetworks( String[] networks )154 encodeNetworks( 155 String[] networks ) 156 { 157 String net_str = ""; 158 159 if ( networks != null && networks.length > 0 ){ 160 161 for ( String net: networks ){ 162 163 if ( net == AENetworkClassifier.AT_PUBLIC && networks.length == 1 ){ 164 165 break; 166 } 167 168 net_str += "&net=" + net; 169 } 170 } 171 172 return( net_str ); 173 } 174 175 public static byte[] extractHash( String magnet_uri )176 extractHash( 177 String magnet_uri ) 178 { 179 magnet_uri = magnet_uri.toLowerCase( Locale.US ); 180 181 int pos = magnet_uri.indexOf( "btih:" ); 182 183 if ( pos > 0 ){ 184 185 magnet_uri = magnet_uri.substring( pos+5 ); 186 187 pos = magnet_uri.indexOf( '&' ); 188 189 if ( pos != -1 ){ 190 191 magnet_uri = magnet_uri.substring( 0, pos ); 192 } 193 194 return( decodeSHA1Hash( magnet_uri )); 195 } 196 197 return( null ); 198 } 199 200 public static Set<String> extractNetworks( String[] magnet_uri )201 extractNetworks( 202 String[] magnet_uri ) 203 { 204 String magnet_uri_in = magnet_uri[0]; 205 206 Set<String> result = new HashSet<String>(); 207 208 int pos = magnet_uri_in.indexOf( '?' ); 209 210 if ( pos != -1 ){ 211 212 String magnet_uri_out = magnet_uri_in.substring( 0, pos+1 ); 213 214 String[] bits = magnet_uri_in.substring( pos+1 ).split( "&" ); 215 216 for ( String bit: bits ){ 217 218 String[] temp = bit.split( "=", 2 ); 219 220 boolean remove = false; 221 222 if ( temp.length == 2 ){ 223 224 String lhs = temp[0]; 225 226 if ( lhs.equalsIgnoreCase( "net" )){ 227 228 String rhs = decode( temp[1] ); 229 230 result.add( AENetworkClassifier.internalise( rhs )); 231 232 remove = true; 233 } 234 } 235 236 if ( !remove ){ 237 238 if ( !magnet_uri_out.endsWith( "?" )){ 239 240 magnet_uri_out += "&"; 241 } 242 243 magnet_uri_out += bit; 244 } 245 } 246 247 if ( result.size() > 0 ){ 248 249 magnet_uri[0] = magnet_uri_out; 250 } 251 } 252 253 return( result ); 254 } 255 256 public static String getMagnetURI( Download download )257 getMagnetURI( 258 Download download ) 259 { 260 return( getMagnetURI( PluginCoreUtils.unwrap(download))); 261 } 262 263 public static String getMagnetURI( Download download, int max_name_len )264 getMagnetURI( 265 Download download, 266 int max_name_len ) 267 { 268 return( getMagnetURI( PluginCoreUtils.unwrap(download), max_name_len )); 269 } 270 271 public static String getMagnetURI( DownloadManager dm )272 getMagnetURI( 273 DownloadManager dm ) 274 { 275 return( getMagnetURI( dm, Integer.MAX_VALUE )); 276 } 277 278 public static String getMagnetURI( DownloadManager dm, int max_name_len )279 getMagnetURI( 280 DownloadManager dm, 281 int max_name_len ) 282 { 283 if ( dm == null ){ 284 285 return( null ); 286 } 287 288 TOTorrent to_torrent = dm.getTorrent(); 289 290 if ( to_torrent == null ){ 291 292 return( null ); 293 } 294 295 String name = dm.getDisplayName(); 296 297 if ( name.length() > max_name_len ){ 298 299 name = name.substring( 0, max_name_len-3) + "..."; 300 } 301 302 String magnet_uri = getMagnetURI( name, PluginCoreUtils.wrap( to_torrent )); 303 304 String[] networks = dm.getDownloadState().getNetworks(); 305 306 magnet_uri += encodeNetworks( networks ); 307 308 return( magnet_uri ); 309 } 310 311 public static String getMagnetURI( String name, Torrent torrent )312 getMagnetURI( 313 String name, 314 Torrent torrent ) 315 { 316 String magnet_str = getMagnetURI( torrent.getHash()); 317 318 magnet_str += encodeName( name); 319 320 List<String> tracker_urls = new ArrayList<String>(); 321 322 URL announce_url = torrent.getAnnounceURL(); 323 324 if ( announce_url != null ){ 325 326 if ( !TorrentUtils.isDecentralised( announce_url )){ 327 328 tracker_urls.add( announce_url.toExternalForm()); 329 } 330 } 331 332 TorrentAnnounceURLList list = torrent.getAnnounceURLList(); 333 334 TorrentAnnounceURLListSet[] sets = list.getSets(); 335 336 for ( TorrentAnnounceURLListSet set: sets ){ 337 338 URL[] set_urls = set.getURLs(); 339 340 if ( set_urls.length > 0 ){ 341 342 URL set_url = set_urls[0]; 343 344 if ( !TorrentUtils.isDecentralised( set_url )){ 345 346 String str = set_url.toExternalForm(); 347 348 if ( !tracker_urls.contains( str )){ 349 350 tracker_urls.add( str ); 351 } 352 } 353 } 354 } 355 356 for ( String str: tracker_urls ){ 357 358 magnet_str += "&tr=" + UrlUtils.encode( str ); 359 } 360 361 List<String> ws_urls = new ArrayList<String>(); 362 363 Object obj = torrent.getAdditionalProperty( "url-list" ); 364 365 if ( obj instanceof byte[] ){ 366 367 try{ 368 ws_urls.add( new URL( new String((byte[])obj, "UTF-8" )).toExternalForm()); 369 370 }catch( Throwable e ){ 371 } 372 }else if ( obj instanceof List ){ 373 374 for ( Object o: (List)obj ){ 375 376 try{ 377 if (o instanceof byte[]) { 378 ws_urls.add( new URL( new String((byte[])o, "UTF-8" )).toExternalForm()); 379 } else if (o instanceof String) { 380 ws_urls.add( new URL((String) o).toExternalForm()); 381 } 382 383 }catch( Throwable e ){ 384 } 385 } 386 } else if ( obj instanceof String ) { 387 try{ 388 ws_urls.add(new URL((String) obj).toExternalForm()); 389 }catch( Throwable e ){ 390 } 391 } 392 393 for ( String str: ws_urls ){ 394 395 magnet_str += "&ws=" + UrlUtils.encode( str ); 396 } 397 398 return( magnet_str ); 399 } 400 /** 401 * returns magnet uri if input is base 32 or base 16 encoded sha1 hash, null otherwise 402 * @param base_hash 403 * @return 404 */ 405 406 public static String normaliseMagnetURI( String base_hash )407 normaliseMagnetURI( 408 String base_hash ) 409 { 410 byte[] hash = decodeSHA1Hash( base_hash ); 411 412 if ( hash != null ){ 413 414 return( getMagnetURI( hash )); 415 } 416 417 return( null ); 418 } 419 420 public static byte[] decodeSHA1Hash( String str )421 decodeSHA1Hash( 422 String str ) 423 { 424 if ( str == null ){ 425 426 return( null ); 427 } 428 429 str = str.trim(); 430 431 byte[] hash = null; 432 433 try{ 434 if ( str.length() == 40 ){ 435 436 hash = ByteFormatter.decodeString( str ); 437 438 }else if ( str.length() == 32 ){ 439 440 hash = Base32.decode( str ); 441 } 442 }catch( Throwable e ){ 443 } 444 445 if ( hash != null ){ 446 447 if ( hash.length != 20 ){ 448 449 hash = null; 450 } 451 } 452 453 return( hash ); 454 } 455 456 /** 457 * test string for possibility that it's an URL. Considers 40 byte hex 458 * strings as URLs 459 * 460 * @param sURL 461 * @return 462 */ isURL(String sURL)463 public static boolean isURL(String sURL) { 464 return parseTextForURL(sURL, true) != null; 465 } 466 isURL(String sURL, boolean bGuess)467 public static boolean isURL(String sURL, boolean bGuess) { 468 return parseTextForURL(sURL, true, bGuess) != null; 469 } 470 parseTextForURL(String text, boolean accept_magnets)471 public static String parseTextForURL(String text, boolean accept_magnets) { 472 return parseTextForURL(text, accept_magnets, true); 473 } 474 475 public static String getURL( String text )476 getURL( 477 String text ) 478 { 479 return( parseTextForURL(text, false, false )); 480 } 481 parseTextForURL(String text, boolean accept_magnets, boolean guess)482 public static String parseTextForURL(String text, boolean accept_magnets, 483 boolean guess) { 484 485 if (text == null || text.length() < 5) { 486 return null; 487 } 488 489 text = text.trim(); 490 491 if ( text.startsWith( "azplug:" )){ 492 493 return( text ); 494 } 495 496 if ( text.startsWith( "chat:" )){ 497 498 return( "azplug:?id=azbuddy&arg=" + UrlUtils.encode( text )); 499 } 500 501 if ( text.startsWith( "tor:" )){ 502 503 String href = parseTextForURL(text.substring(4), false, false ); 504 if (href != null) { 505 return( "tor:" + href ); 506 } 507 } 508 509 String href = parseHTMLforURL(text); 510 if (href != null) { 511 return href; 512 } 513 514 try { 515 text = text.trim(); 516 text = decodeIfNeeded(text); 517 } catch (Exception e) { 518 // sometimes fires a IllegalArgumentException 519 // catch everything and ignore. 520 } 521 522 String textLower; 523 try { 524 textLower = text.toLowerCase(); 525 } catch (Throwable e) { 526 textLower = text; 527 } 528 int max = accept_magnets ? prefixes.length : MAGNETURL_STARTS_AT; 529 int end = -1; 530 int start = textLower.length(); 531 String strURL = null; 532 for (int i = 0; i < max; i++) { 533 final int testBegin = textLower.indexOf(prefixes[i]); 534 if (testBegin >= 0 && testBegin < start) { 535 end = text.indexOf("\n", testBegin + prefixes[i].length()); 536 String strURLTest = (end >= 0) ? text.substring(testBegin, end - 1) 537 : text.substring(testBegin); 538 try { 539 URL parsedURL = new URL(strURLTest); 540 strURL = parsedURL.toExternalForm(); 541 } catch (MalformedURLException e1) { 542 e1.printStackTrace(); 543 if (i >= MAGNETURL_STARTS_AT) { 544 strURL = strURLTest; 545 } 546 } 547 } 548 } 549 if (strURL != null) { 550 return strURL; 551 } 552 553 if (new File(text).exists()) { 554 return null; 555 } 556 557 // be lenient for raw anon addresses 558 559 try{ 560 URL u = new URL( "http://" + text ); 561 562 String host = u.getHost(); 563 564 if ( host != null && AENetworkClassifier.categoriseAddress( host ) != AENetworkClassifier.AT_PUBLIC ){ 565 566 return( u.toExternalForm()); 567 } 568 }catch( Throwable e ){ 569 } 570 571 if (accept_magnets 572 && (text.startsWith("bc://") || text.startsWith("bctp://"))) { 573 return parseTextForMagnets(text); 574 } 575 576 // hack to support appending args to raw hashes 577 578 String text_prefix = text; 579 String text_suffix = ""; 580 581 int a_pos = text_prefix.indexOf( '?' ); 582 if ( a_pos == -1 ){ 583 a_pos = text_prefix.indexOf( '&' ); 584 } 585 if ( a_pos != -1 ){ 586 String args = text_prefix.substring( a_pos+1 ).trim(); 587 if ( args.contains( "=" )){ 588 int s_pos = args.indexOf(' '); 589 if ( s_pos != -1 ){ 590 args = args.substring( 0, s_pos ); 591 } 592 text_prefix = text_prefix.substring( 0, a_pos ); 593 text_suffix = "&" + args; 594 } 595 } 596 597 // accept raw hash of 40 hex chars 598 if (accept_magnets ){ 599 600 if ( text_prefix.matches("^[a-fA-F0-9]{40}$")) { 601 602 // convert from HEX to raw bytes 603 byte[] infohash = ByteFormatter.decodeString(text_prefix.toUpperCase()); 604 // convert to BASE32 605 return "magnet:?xt=urn:btih:" + Base32.encode(infohash) + text_suffix; 606 } 607 608 String temp_text = text_prefix.replaceAll( "\\s+", "" ); 609 610 if ( temp_text.matches("^[a-fA-F0-9]{40}$")) { 611 612 // convert from HEX to raw bytes 613 byte[] infohash = ByteFormatter.decodeString(temp_text.toUpperCase()); 614 // convert to BASE32 615 return "magnet:?xt=urn:btih:" + Base32.encode(infohash) + text_suffix; 616 } 617 } 618 619 // accept raw hash of 32 base-32 chars 620 if (accept_magnets && text_prefix.matches("^[a-zA-Z2-7]{32}$")) { 621 return "magnet:?xt=urn:btih:" + text_prefix + text_suffix; 622 } 623 624 // javascript:loadOrAlert('WVOPRHRPFSCLAW7UWHCXCH7QNQIU6TWG') 625 626 // accept raw hash of 32 base-32 chars, with garbage around it 627 if (accept_magnets && guess) { 628 Pattern pattern = Pattern.compile("[^a-zA-Z2-7][a-zA-Z2-7]{32}[^a-zA-Z2-7]"); 629 Matcher matcher = pattern.matcher(text); 630 if (matcher.find()) { 631 String hash = text.substring(matcher.start() + 1, matcher.start() + 33); 632 return "magnet:?xt=urn:btih:" + hash; 633 } 634 635 pattern = Pattern.compile("[^a-fA-F0-9][a-fA-F0-9]{40}[^a-fA-F0-9]"); 636 matcher = pattern.matcher(text); 637 if (matcher.find()) { 638 String hash = text.substring(matcher.start() + 1, matcher.start() + 41); 639 // convert from HEX to raw bytes 640 byte[] infohash = ByteFormatter.decodeString(hash.toUpperCase()); 641 // convert to BASE32 642 return "magnet:?xt=urn:btih:" + Base32.encode(infohash); 643 } 644 } 645 646 return null; 647 } 648 649 public static String parseTextForMagnets( String text )650 parseTextForMagnets( 651 String text ) 652 { 653 if (text.startsWith("magnet:") || text.startsWith( "maggot:" )){ 654 return text; 655 } 656 657 // accept raw hash of 40 hex chars 658 if (text.matches("^[a-fA-F0-9]{40}$")) { 659 // convert from HEX to raw bytes 660 byte[] infohash = ByteFormatter.decodeString(text.toUpperCase()); 661 // convert to BASE32 662 return "magnet:?xt=urn:btih:" + Base32.encode(infohash); 663 } 664 665 String temp_text = text.replaceAll( "\\s+", "" ); 666 if (temp_text.matches("^[a-fA-F0-9]{40}$")) { 667 // convert from HEX to raw bytes 668 byte[] infohash = ByteFormatter.decodeString(temp_text.toUpperCase()); 669 // convert to BASE32 670 return "magnet:?xt=urn:btih:" + Base32.encode(infohash); 671 } 672 673 // accept raw hash of 32 base-32 chars 674 if (text.matches("^[a-zA-Z2-7]{32}$")) { 675 return "magnet:?xt=urn:btih:" + text; 676 } 677 678 Pattern pattern; 679 Matcher matcher; 680 681 pattern = Pattern.compile("magnet:\\?[a-z%0-9=_:&.]+", Pattern.CASE_INSENSITIVE); 682 matcher = pattern.matcher(text); 683 if (matcher.find()) { 684 return matcher.group(); 685 } 686 687 pattern = Pattern.compile("maggot://[a-z0-9]+:[a-z0-9]", Pattern.CASE_INSENSITIVE); 688 matcher = pattern.matcher(text); 689 if (matcher.find()) { 690 return matcher.group(); 691 } 692 693 pattern = Pattern.compile("bc://bt/([a-z0-9=\\+/]+)", Pattern.CASE_INSENSITIVE); 694 matcher = pattern.matcher(text.replaceAll(" ", "+")); 695 if (matcher.find()) { 696 String base64 = matcher.group(1); 697 byte[] decode = Base64.decode(base64); 698 if (decode != null && decode.length > 0) { 699 // Format is AA/<name>/<size>/<hash>/ZZ 700 try { 701 String decodeString = new String(decode, "utf8"); 702 pattern = Pattern.compile("AA.*/(.*)/ZZ", Pattern.CASE_INSENSITIVE); 703 matcher = pattern.matcher(decodeString); 704 if (matcher.find()) { 705 String hash = matcher.group(1); 706 String magnet = parseTextForMagnets(hash); 707 if (magnet != null) { 708 pattern = Pattern.compile("AA/(.*)/[0-9]+", Pattern.CASE_INSENSITIVE); 709 matcher = pattern.matcher(decodeString); 710 if (matcher.find()) { 711 String name = matcher.group(1); 712 return magnet + "&dn=" + encode(name); 713 } 714 return magnet; 715 } 716 } 717 } catch (UnsupportedEncodingException e) { 718 } 719 } 720 } 721 722 pattern = Pattern.compile("bctp://task/(.*)", Pattern.CASE_INSENSITIVE); 723 matcher = pattern.matcher(text); 724 if (matcher.find()) { 725 // Format is <name>/<size>/<hash> 726 String decodeString = matcher.group(1); 727 String magnet = parseTextForMagnets(decodeString); 728 if (magnet != null) { 729 pattern = Pattern.compile("(.*)/[0-9]+", Pattern.CASE_INSENSITIVE); 730 matcher = pattern.matcher(decodeString); 731 if (matcher.find()) { 732 String name = matcher.group(1); 733 return magnet + "&dn=" + encode(name); 734 } 735 return magnet; 736 } 737 } 738 739 // accept raw hash of 32 base-32 chars, with garbage around it 740 if (true) { 741 text = "!" + text + "!"; 742 pattern = Pattern.compile("[^a-zA-Z2-7][a-zA-Z2-7]{32}[^a-zA-Z2-7]"); 743 matcher = pattern.matcher(text); 744 if (matcher.find()) { 745 String hash = text.substring(matcher.start() + 1, matcher.start() + 33); 746 return "magnet:?xt=urn:btih:" + hash; 747 } 748 749 pattern = Pattern.compile("[^a-fA-F0-9][a-fA-F0-9]{40}[^a-fA-F0-9]"); 750 matcher = pattern.matcher(text); 751 if (matcher.find()) { 752 String hash = text.substring(matcher.start() + 1, matcher.start() + 41); 753 // convert from HEX to raw bytes 754 byte[] infohash = ByteFormatter.decodeString(hash.toUpperCase()); 755 // convert to BASE32 756 return "magnet:?xt=urn:btih:" + Base32.encode(infohash); 757 } 758 } 759 760 return( null ); 761 } 762 parseHTMLforURL(String text)763 private static String parseHTMLforURL(String text) { 764 if (text == null) { 765 return null; 766 } 767 768 // examples: 769 // <A HREF=http://abc.om/moo>test</a> 770 // <A style=cow HREF="http://abc.om/moo">test</a> 771 // <a href="http://www.gnu.org/licenses/fdl.html" target="_top">moo</a> 772 773 Pattern pat = Pattern.compile("<.*a\\s++.*href=\"?([^\\'\"\\s>]++).*", 774 Pattern.CASE_INSENSITIVE); 775 Matcher m = pat.matcher(text); 776 if (m.find()) { 777 String sURL = m.group(1); 778 try { 779 sURL = decodeIfNeeded(sURL); 780 } catch (Exception e) { 781 // sometimes fires a IllegalArgumentException 782 // catch everything and ignore. 783 } 784 return sURL; 785 } 786 787 return null; 788 } 789 790 791 792 /** 793 * Like URLEncoder.encode, except translates spaces into %20 instead of + 794 * @param s 795 * @return 796 */ encode(String s)797 public static String encode(String s) { 798 if (s == null) { 799 return ""; 800 } 801 try { 802 return URLEncoder.encode(s, "UTF-8").replaceAll("\\+", "%20"); 803 } catch (UnsupportedEncodingException e) { 804 return URLEncoder.encode(s).replaceAll("\\+", "%20"); 805 } 806 } 807 decode(String s)808 public static String decode(String s) { 809 if (s == null) { 810 return ""; 811 } 812 try { 813 try{ 814 return( URLDecoder.decode(s, "UTF-8")); 815 816 }catch( IllegalArgumentException e ){ 817 818 // handle truncated encodings somewhat gracefully 819 820 int pos = s.lastIndexOf( "%" ); 821 822 if ( pos >= s.length()-2 ){ 823 824 return( URLDecoder.decode(s.substring( 0, pos ), "UTF-8")); 825 } 826 827 throw( e ); 828 } 829 } catch (UnsupportedEncodingException e) { 830 831 return( URLDecoder.decode(s)); 832 } 833 } 834 835 /** 836 * Unfortunately we have code that mindlessly decoded URLs (FileDownloadWindow) which borked (in the case I discovered) magnet uris with encoded 837 * parameters (e.g. the &tr= parameter) - doing so screws stuff up later if, for example, the parameter included an encoded '&' 838 * @param s 839 * @return 840 */ 841 842 public static String decodeIfNeeded( String s )843 decodeIfNeeded( 844 String s ) 845 { 846 if ( s == null ){ 847 848 return( "" ); 849 } 850 851 try{ 852 // of course someone prolly added the blind URLDecode for a reason so try and just deal with the borkage 853 // which means looking for &a=b elements and ensure we don't URLDecode the 'b' 854 855 // only have issues if there's a naked '?' there 856 857 int q_pos = s.indexOf( '?' ); 858 int a_pos = s.indexOf( '&' ); 859 860 if ( q_pos == -1 && a_pos == -1 ){ 861 862 return( decode( s )); 863 } 864 865 int start = Math.min( q_pos, a_pos ); 866 867 return( decode( s.substring( 0, start )) + s.substring( start )); 868 869 }catch( Throwable e ){ 870 871 return( s ); 872 } 873 } 874 escapeXML(String s)875 public static String escapeXML(String s) { 876 if (s == null) { 877 return ""; 878 } 879 String ret = s; 880 for (int i = 0; i < XMLescapes.length; i++) { 881 String[] escapeEntry = (String[])XMLescapes[i]; 882 ret = ret.replaceAll(escapeEntry[0], escapeEntry[1]); 883 } 884 return ret; 885 } 886 unescapeXML(String s)887 public static String unescapeXML(String s) { 888 if (s == null) { 889 return ""; 890 } 891 String ret = s; 892 for (int i = 0; i < XMLescapes.length; i++) { 893 String[] escapeEntry = (String[])XMLescapes[i]; 894 ret = ret.replaceAll(escapeEntry[1], escapeEntry[0]); 895 } 896 return ret; 897 } 898 899 public static String convertIPV6Host( String host )900 convertIPV6Host( 901 String host ) 902 { 903 if ( host.indexOf(':') != -1 ){ 904 905 int zone_index = host.indexOf( '%' ); 906 907 if ( zone_index != -1 ){ 908 909 host = host.substring( 0, zone_index ) + encode( host.substring( zone_index )); 910 } 911 912 return( "[" + host + "]" ); 913 } 914 915 return( host ); 916 } 917 918 public static String expandIPV6Host( String host )919 expandIPV6Host( 920 String host ) 921 { 922 if ( host.indexOf(':') != -1 ){ 923 924 try{ 925 return( InetAddress.getByAddress(InetAddress.getByName( host ).getAddress()).getHostAddress()); 926 927 }catch( Throwable e ){ 928 929 Debug.printStackTrace(e); 930 } 931 } 932 933 return( host ); 934 } 935 936 public static void connectWithTimeout( final URLConnection connection, long connect_timeout )937 connectWithTimeout( 938 final URLConnection connection, 939 long connect_timeout ) 940 941 throws IOException 942 { 943 connectWithTimeouts( connection, connect_timeout, -1 ); 944 } 945 946 public static void connectWithTimeouts( final URLConnection connection, long connect_timeout, long read_timeout )947 connectWithTimeouts( 948 final URLConnection connection, 949 long connect_timeout, 950 long read_timeout ) 951 952 throws IOException 953 { 954 if ( connect_timeout != -1 ){ 955 956 connection.setConnectTimeout( (int)connect_timeout ); 957 } 958 959 if ( read_timeout != -1 ){ 960 961 connection.setReadTimeout( (int)read_timeout ); 962 } 963 964 connection.connect(); 965 } 966 967 private static String last_headers = COConfigurationManager.getStringParameter( "metasearch.web.last.headers", null ); 968 969 // private static final String default_headers = "SG9zdDogbG9jYWxob3N0OjQ1MTAwClVzZXItQWdlbnQ6IE1vemlsbGEvNS4wIChXaW5kb3dzOyBVOyBXaW5kb3dzIE5UIDUuMTsgZW4tVVM7IHJ2OjEuOC4xLjE0KSBHZWNrby8yMDA4MDQwNCBGaXJlZm94LzIuMC4wLjE0CkFjY2VwdDogdGV4dC94bWwsYXBwbGljYXRpb24veG1sLGFwcGxpY2F0aW9uL3hodG1sK3htbCx0ZXh0L2h0bWw7cT0wLjksdGV4dC9wbGFpbjtxPTAuOCxpbWFnZS9wbmcsKi8qO3E9MC41CkFjY2VwdC1MYW5ndWFnZTogZW4tdXMsZW47cT0wLjUKQWNjZXB0LUVuY29kaW5nOiBnemlwLGRlZmxhdGUKQWNjZXB0LUNoYXJzZXQ6IElTTy04ODU5LTEsdXRmLTg7cT0wLjcsKjtxPTAuNwpLZWVwLUFsaXZlOiAzMDAKQ29ubmVjdGlvbjoga2VlcC1hbGl2ZQ=="; 970 private static final String default_headers = "QWNjZXB0OiB0ZXh0L2h0bWwsYXBwbGljYXRpb24veGh0bWwreG1sLGFwcGxpY2F0aW9uL3htbDtxPTAuOSwqLyo7cT0wLjgKQWNjZXB0LUNoYXJzZXQ6IElTTy04ODU5LTEsdXRmLTg7cT0wLjcsKjtxPTAuMwpBY2NlcHQtRW5jb2Rpbmc6IGd6aXAsZGVmbGF0ZQpBY2NlcHQtTGFuZ3VhZ2U6IGVuLVVTLGVuO3E9MC44CkNhY2hlLUNvbnRyb2w6IG1heC1hZ2U9MApDb25uZWN0aW9uOiBrZWVwLWFsaXZlClVzZXItQWdlbnQ6IE1vemlsbGEvNS4wIChXaW5kb3dzIE5UIDYuMTsgV09XNjQpIEFwcGxlV2ViS2l0LzUzNi4xMSAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS8yMC4wLjExMzIuNDcgU2FmYXJpLzUzNi4xMQ=="; 971 972 public static void setBrowserHeaders( ResourceDownloader rd, String referer )973 setBrowserHeaders( 974 ResourceDownloader rd, 975 String referer ) 976 { 977 setBrowserHeaders( rd, null, referer ); 978 } 979 980 public static void setBrowserHeaders( ResourceDownloader rd, String encoded_headers, String referer )981 setBrowserHeaders( 982 ResourceDownloader rd, 983 String encoded_headers, 984 String referer ) 985 { 986 String headers_to_use = getBrowserHeadersToUse( encoded_headers ); 987 988 try{ 989 String header_string = new String( Base64.decode( headers_to_use ), "UTF-8" ); 990 991 String[] headers = header_string.split( "\n" ); 992 993 for (int i=0;i<headers.length;i++ ){ 994 995 String header = headers[i]; 996 997 int pos = header.indexOf( ':' ); 998 999 if ( pos != -1 ){ 1000 1001 String lhs = header.substring(0,pos).trim(); 1002 String rhs = header.substring(pos+1).trim(); 1003 1004 if ( !( lhs.equalsIgnoreCase( "Host") || 1005 lhs.equalsIgnoreCase( "Referer" ))){ 1006 1007 rd.setProperty( "URL_" + lhs, rhs ); 1008 } 1009 } 1010 } 1011 1012 if ( referer != null && referer.length() > 0 ){ 1013 1014 rd.setProperty( "URL_Referer", referer ); 1015 } 1016 }catch( Throwable e ){ 1017 } 1018 } 1019 1020 public static void setBrowserHeaders( ResourceUploader ru, String encoded_headers, String referer )1021 setBrowserHeaders( 1022 ResourceUploader ru, 1023 String encoded_headers, 1024 String referer ) 1025 { 1026 String headers_to_use = getBrowserHeadersToUse( encoded_headers ); 1027 1028 try{ 1029 String header_string = new String( Base64.decode( headers_to_use ), "UTF-8" ); 1030 1031 String[] headers = header_string.split( "\n" ); 1032 1033 for (int i=0;i<headers.length;i++ ){ 1034 1035 String header = headers[i]; 1036 1037 int pos = header.indexOf( ':' ); 1038 1039 if ( pos != -1 ){ 1040 1041 String lhs = header.substring(0,pos).trim(); 1042 String rhs = header.substring(pos+1).trim(); 1043 1044 if ( !( lhs.equalsIgnoreCase( "Host") || 1045 lhs.equalsIgnoreCase( "Referer" ))){ 1046 1047 ru.setProperty( "URL_" + lhs, rhs ); 1048 } 1049 } 1050 } 1051 1052 if ( referer != null && referer.length() > 0 ){ 1053 1054 ru.setProperty( "URL_Referer", referer ); 1055 } 1056 }catch( Throwable e ){ 1057 } 1058 } 1059 1060 public static void setBrowserHeaders( URLConnection connection, String referer )1061 setBrowserHeaders( 1062 URLConnection connection, 1063 String referer ) 1064 { 1065 setBrowserHeaders( connection, null, referer ); 1066 } 1067 1068 public static void setBrowserHeaders( URLConnection connection, String encoded_headers, String referer )1069 setBrowserHeaders( 1070 URLConnection connection, 1071 String encoded_headers, 1072 String referer ) 1073 { 1074 String headers_to_use = getBrowserHeadersToUse( encoded_headers ); 1075 1076 try{ 1077 1078 String header_string = new String( Base64.decode( headers_to_use ), "UTF-8" ); 1079 1080 String[] headers = header_string.split( "\n" ); 1081 1082 for (int i=0;i<headers.length;i++ ){ 1083 1084 String header = headers[i]; 1085 1086 int pos = header.indexOf( ':' ); 1087 1088 if ( pos != -1 ){ 1089 1090 String lhs = header.substring(0,pos).trim(); 1091 String rhs = header.substring(pos+1).trim(); 1092 1093 if ( !( lhs.equalsIgnoreCase( "Host") || 1094 lhs.equalsIgnoreCase( "Referer" ))){ 1095 1096 connection.setRequestProperty( lhs, rhs ); 1097 } 1098 } 1099 } 1100 1101 if ( referer != null && referer.length() > 0 ){ 1102 1103 connection.setRequestProperty( "Referer", referer ); 1104 } 1105 }catch( Throwable e ){ 1106 } 1107 } 1108 1109 public static Map getBrowserHeaders( String referer )1110 getBrowserHeaders( 1111 String referer ) 1112 { 1113 String headers_to_use = getBrowserHeadersToUse( null ); 1114 1115 Map result = new HashMap(); 1116 1117 try{ 1118 1119 String header_string = new String( Base64.decode( headers_to_use ), "UTF-8" ); 1120 1121 String[] headers = header_string.split( "\n" ); 1122 1123 for (int i=0;i<headers.length;i++ ){ 1124 1125 String header = headers[i]; 1126 1127 int pos = header.indexOf( ':' ); 1128 1129 if ( pos != -1 ){ 1130 1131 String lhs = header.substring(0,pos).trim(); 1132 String rhs = header.substring(pos+1).trim(); 1133 1134 if ( !( lhs.equalsIgnoreCase( "Host") || 1135 lhs.equalsIgnoreCase( "Referer" ))){ 1136 1137 result.put( lhs, rhs ); 1138 } 1139 } 1140 } 1141 1142 if ( referer != null && referer.length() > 0){ 1143 1144 result.put( "Referer", referer ); 1145 } 1146 }catch( Throwable e ){ 1147 } 1148 1149 return( result ); 1150 } 1151 1152 private static String getBrowserHeadersToUse( String encoded_headers )1153 getBrowserHeadersToUse( 1154 String encoded_headers ) 1155 { 1156 String headers_to_use = encoded_headers; 1157 1158 synchronized( UrlUtils.class ){ 1159 1160 if ( headers_to_use == null ){ 1161 1162 if ( last_headers != null ){ 1163 1164 headers_to_use = last_headers; 1165 1166 }else{ 1167 1168 headers_to_use = default_headers; 1169 } 1170 }else{ 1171 1172 if ( last_headers == null || !headers_to_use.equals( last_headers )){ 1173 1174 COConfigurationManager.setParameter( "metasearch.web.last.headers", headers_to_use ); 1175 } 1176 1177 last_headers = headers_to_use; 1178 } 1179 } 1180 1181 return( headers_to_use ); 1182 } 1183 queryHasParameter(String query_string, String param_name, boolean case_sensitive)1184 public static boolean queryHasParameter(String query_string, String param_name, boolean case_sensitive) { 1185 if (!case_sensitive) { 1186 query_string = query_string.toLowerCase(); 1187 param_name = param_name.toLowerCase(); 1188 } 1189 if (query_string.charAt(0) == '?') { 1190 query_string = '&' + query_string.substring(1); 1191 } 1192 else if (query_string.charAt(0) != '&') { 1193 query_string = '&' + query_string; 1194 } 1195 1196 return query_string.indexOf("&" + param_name + "=") != -1; 1197 } 1198 1199 public static boolean containsPasskey( URL url )1200 containsPasskey( 1201 URL url ) 1202 { 1203 if ( url == null ){ 1204 1205 return( false ); 1206 } 1207 1208 String url_str = url.toExternalForm(); 1209 1210 return( url_str.matches(".*[0-9a-z]{20,40}.*")); 1211 } 1212 1213 public static URL setPort( URL u, int port )1214 setPort( 1215 URL u, 1216 int port ) 1217 { 1218 if ( port == -1 ){ 1219 port = u.getDefaultPort(); 1220 } 1221 StringBuffer result = new StringBuffer(); 1222 result.append(u.getProtocol()); 1223 result.append(":"); 1224 String authority=u.getAuthority(); 1225 if (authority != null && authority.length() > 0) { 1226 result.append("//"); 1227 int pos = authority.indexOf( '@' ); 1228 if ( pos != -1 ){ 1229 result.append(authority.substring(0,pos+1)); 1230 authority = authority.substring(pos+1); 1231 } 1232 pos = authority.lastIndexOf(':'); 1233 if ( pos == -1 ){ 1234 if ( port > 0 ){ 1235 result.append(authority + ":" + port ); 1236 }else{ 1237 result.append(authority); 1238 } 1239 }else{ 1240 if ( port > 0 ){ 1241 result.append(authority.substring(0,pos+1) + port ); 1242 }else{ 1243 result.append(authority.substring(0,pos)); 1244 } 1245 } 1246 } 1247 if (u.getPath() != null) { 1248 result.append(u.getPath()); 1249 } 1250 if (u.getQuery() != null) { 1251 result.append('?'); 1252 result.append(u.getQuery()); 1253 } 1254 if (u.getRef() != null) { 1255 result.append("#"); 1256 result.append(u.getRef()); 1257 } 1258 try{ 1259 return( new URL( result.toString())); 1260 }catch( Throwable e ){ 1261 Debug.out(e); 1262 return(u); 1263 } 1264 } 1265 1266 public static URL setHost( URL u, String host )1267 setHost( 1268 URL u, 1269 String host ) 1270 { 1271 StringBuffer result = new StringBuffer(); 1272 result.append(u.getProtocol()); 1273 result.append(":"); 1274 String authority=u.getAuthority(); 1275 if (authority != null && authority.length() > 0) { 1276 result.append("//"); 1277 int pos = authority.indexOf( '@' ); 1278 if ( pos != -1 ){ 1279 result.append(authority.substring(0,pos+1)); 1280 authority = authority.substring(pos+1); 1281 } 1282 pos = authority.lastIndexOf(':'); 1283 if ( pos == -1 ){ 1284 result.append(host ); 1285 }else{ 1286 result.append(host + authority.substring(pos)); 1287 } 1288 } 1289 if (u.getPath() != null) { 1290 result.append(u.getPath()); 1291 } 1292 if (u.getQuery() != null) { 1293 result.append('?'); 1294 result.append(u.getQuery()); 1295 } 1296 if (u.getRef() != null) { 1297 result.append("#"); 1298 result.append(u.getRef()); 1299 } 1300 try{ 1301 return( new URL( result.toString())); 1302 }catch( Throwable e ){ 1303 Debug.out(e); 1304 return(u); 1305 } 1306 } 1307 1308 public static URL setProtocol( URL u, String protocol )1309 setProtocol( 1310 URL u, 1311 String protocol ) 1312 { 1313 String str = u.toExternalForm(); 1314 1315 int pos = str.indexOf( ":" ); 1316 1317 try{ 1318 return( new URL( protocol + str.substring( pos ))); 1319 1320 }catch( Throwable e ){ 1321 1322 Debug.out( e ); 1323 1324 return( u ); 1325 } 1326 } 1327 1328 public static URL getBaseURL( URL u )1329 getBaseURL( 1330 URL u ) 1331 { 1332 StringBuffer result = new StringBuffer(); 1333 result.append(u.getProtocol()); 1334 result.append(":"); 1335 String authority=u.getAuthority(); 1336 if (authority != null && authority.length() > 0) { 1337 result.append("//"); 1338 int pos = authority.indexOf( '@' ); 1339 if ( pos != -1 ){ 1340 result.append(authority.substring(0,pos+1)); 1341 authority = authority.substring(pos+1); 1342 } 1343 pos = authority.lastIndexOf(':'); 1344 int port = u.getPort(); 1345 if ( port == -1 ){ 1346 port = u.getDefaultPort(); 1347 } 1348 if ( pos == -1 ){ 1349 result.append(authority + ":" + port ); 1350 }else{ 1351 result.append(authority.substring(0,pos+1) + port ); 1352 } 1353 } 1354 1355 try{ 1356 return( new URL( result.toString())); 1357 }catch( Throwable e ){ 1358 Debug.out(e); 1359 return(u); 1360 } 1361 } 1362 1363 public static String getCanonicalString( URL url )1364 getCanonicalString( 1365 URL url ) 1366 { 1367 String protocol = url.getProtocol(); 1368 1369 if ( !protocol.equals( protocol.toLowerCase( Locale.US ))){ 1370 1371 protocol = protocol.toLowerCase( Locale.US ); 1372 1373 url = UrlUtils.setProtocol( url, protocol ); 1374 } 1375 1376 int port = url.getPort(); 1377 1378 if ( protocol.equals( "http" ) || protocol.equals( "https" )){ 1379 1380 if ( port == url.getDefaultPort()){ 1381 1382 url = UrlUtils.setPort( url, 0 ); 1383 } 1384 }else{ 1385 1386 if ( port == -1 ){ 1387 1388 url = UrlUtils.setPort( url, url.getDefaultPort()); 1389 } 1390 } 1391 1392 return( url.toString()); 1393 } 1394 1395 /** 1396 * Returns an explicit IPv4 url if the supplied one has both IPv6 and IPv4 addresses 1397 * @param url 1398 * @return 1399 */ 1400 1401 public static URL getIPV4Fallback( URL url )1402 getIPV4Fallback( 1403 URL url ) 1404 { 1405 try{ 1406 InetAddress[] addresses = AddressUtils.getAllByName( url.getHost()); 1407 1408 if ( addresses.length > 0 ){ 1409 1410 InetAddress ipv4 = null; 1411 InetAddress ipv6 = null; 1412 1413 for ( InetAddress a: addresses ){ 1414 1415 if ( a instanceof Inet4Address ){ 1416 1417 ipv4 = a; 1418 1419 }else{ 1420 1421 ipv6 = a; 1422 } 1423 } 1424 1425 if ( ipv4 != null && ipv6 != null ){ 1426 1427 url = UrlUtils.setHost( url, ipv4.getHostAddress()); 1428 1429 return( url ); 1430 } 1431 } 1432 }catch( Throwable f ){ 1433 } 1434 1435 return( null ); 1436 } 1437 1438 public static long getContentLength( URLConnection con )1439 getContentLength( 1440 URLConnection con ) 1441 { 1442 long res = con.getContentLength(); 1443 1444 if ( res == -1 ){ 1445 1446 try{ 1447 String str = con.getHeaderField( "content-length" ); 1448 1449 if ( str != null ){ 1450 1451 res = Long.parseLong( str ); 1452 } 1453 }catch( Throwable e ){ 1454 1455 } 1456 } 1457 1458 return( res ); 1459 } 1460 1461 public static boolean SSLSocketSNIHack( String host_name, SSLSocket socket )1462 SSLSocketSNIHack( 1463 String host_name, 1464 SSLSocket socket ) 1465 { 1466 // http://stackoverflow.com/questions/30817934/extended-server-name-sni-extension-not-sent-with-jdk1-8-0-but-send-with-jdk1-7 1467 // also https://bugs.openjdk.java.net/browse/JDK-8144566 kinda 1468 1469 1470 try{ 1471 Object sni_host_name = Class.forName( "javax.net.ssl.SNIHostName").getConstructor( String.class ).newInstance( host_name ); 1472 1473 List<Object> sni_host_names = new ArrayList<Object>(1); 1474 1475 sni_host_names.add( sni_host_name ); 1476 1477 Object ssl_parameters = SSLSocket.class.getMethod( "getSSLParameters" ).invoke( socket ); 1478 1479 Class ssl_parameters_class = Class.forName( "javax.net.ssl.SSLParameters" ); 1480 1481 ssl_parameters_class.getMethod( "setServerNames", List.class ).invoke( ssl_parameters, sni_host_names ); 1482 1483 socket.getClass().getMethod( "setSSLParameters" , ssl_parameters_class ).invoke( socket, ssl_parameters ); 1484 1485 /* 1486 SNIHostName serverName = new SNIHostName("whatever.com"); 1487 List<SNIServerName> serverNames = new ArrayList<>(1); 1488 serverNames.add(serverName); 1489 1490 SSLParameters params = socket.getSSLParameters(); 1491 params.setServerNames(serverNames); 1492 socket.setSSLParameters(params); 1493 */ 1494 1495 return( true ); 1496 1497 }catch( Throwable e ){ 1498 1499 return( false ); 1500 } 1501 } 1502 1503 public static SSLSocketFactory DHHackIt( final SSLSocketFactory factory )1504 DHHackIt( 1505 final SSLSocketFactory factory ) 1506 { 1507 SSLSocketFactory hack = new 1508 SSLSocketFactory() 1509 { 1510 @Override 1511 public Socket createSocket() throws IOException { 1512 Socket result = factory.createSocket(); 1513 1514 hack( result ); 1515 1516 return( result ); 1517 } 1518 @Override 1519 public Socket createSocket( 1520 InetAddress address, 1521 int port, 1522 InetAddress localAddress, 1523 int localPort) 1524 throws IOException { 1525 Socket result = factory.createSocket( address, port, localAddress, localPort ); 1526 1527 hack( result ); 1528 1529 return( result ); 1530 } 1531 @Override 1532 public Socket createSocket( 1533 InetAddress host, 1534 int port) 1535 throws IOException { 1536 Socket result = factory.createSocket( host, port ); 1537 1538 hack( result ); 1539 1540 return( result ); 1541 } 1542 @Override 1543 public Socket createSocket( 1544 Socket s, 1545 String host, 1546 int port, 1547 boolean autoClose) 1548 throws IOException { 1549 Socket result = factory.createSocket( s, host, port, autoClose ); 1550 1551 hack( result ); 1552 1553 return( result ); 1554 } 1555 @Override 1556 public Socket createSocket( 1557 String host, 1558 int port) 1559 throws IOException, 1560 UnknownHostException { 1561 Socket result = factory.createSocket( host, port ); 1562 1563 hack( result ); 1564 1565 return( result ); 1566 } 1567 @Override 1568 public Socket createSocket( 1569 String host, 1570 int port, 1571 InetAddress localHost, 1572 int localPort) 1573 throws IOException, 1574 UnknownHostException { 1575 Socket result = factory.createSocket( host, port, localHost, localPort ); 1576 1577 hack( result ); 1578 1579 return( result ); 1580 } 1581 @Override 1582 public String[] getDefaultCipherSuites() { 1583 String[] result = factory.getDefaultCipherSuites(); 1584 1585 result = hack( result ); 1586 1587 return( result ); 1588 } 1589 @Override 1590 public String[] getSupportedCipherSuites() { 1591 String[] result = factory.getSupportedCipherSuites(); 1592 1593 result = hack( result ); 1594 1595 return( result ); 1596 } 1597 1598 private void 1599 hack( 1600 Socket socket ) 1601 { 1602 SSLSocket ssl_socket = (SSLSocket)socket; 1603 1604 ssl_socket.setEnabledCipherSuites( hack( ssl_socket.getEnabledCipherSuites())); 1605 } 1606 1607 private String[] 1608 hack( 1609 String[] cs ) 1610 { 1611 List<String> new_cs = new ArrayList<String>(); 1612 1613 for ( String x: cs ){ 1614 1615 if ( x.contains( "_DH_" ) || x.contains( "_DHE_" )){ 1616 1617 }else{ 1618 1619 new_cs.add( x ); 1620 } 1621 } 1622 1623 return( new_cs.toArray(new String[new_cs.size()])); 1624 } 1625 }; 1626 1627 return( hack ); 1628 } 1629 1630 public static void DHHackIt( HttpsURLConnection ssl_con )1631 DHHackIt( 1632 HttpsURLConnection ssl_con ) 1633 { 1634 final SSLSocketFactory factory = ssl_con.getSSLSocketFactory(); 1635 1636 SSLSocketFactory hack = DHHackIt( factory ); 1637 1638 ssl_con.setSSLSocketFactory( hack ); 1639 } 1640 1641 public static Socket connectSocketAndWrite( boolean is_ssl, String target_host, int target_port, byte[] bytes, int connect_timeout, int read_timeout )1642 connectSocketAndWrite( 1643 boolean is_ssl, 1644 String target_host, 1645 int target_port, 1646 byte[] bytes, 1647 int connect_timeout, 1648 int read_timeout ) 1649 1650 throws Exception 1651 { 1652 // some versions of java 6 don't support the creation of unconnected sockets 1653 // which is required to allow connect timeout to be set before connecting 1654 // think it only actually affected SSL because the SSL Factory had a bug 1655 1656 boolean is_java_17_plus = Constants.isJava7OrHigher; 1657 1658 // try without regard for broken versions 1659 1660 try{ 1661 return( connectSocketAndWrite( is_ssl, target_host, target_port, bytes, connect_timeout, read_timeout, false )); 1662 1663 }catch( Exception e ){ 1664 1665 if ( is_java_17_plus ){ 1666 1667 throw( e ); 1668 } 1669 1670 return( connectSocketAndWrite( is_ssl, target_host, target_port, bytes, connect_timeout, read_timeout, true )); 1671 } 1672 } 1673 1674 public static Socket connectSocketAndWrite( boolean is_ssl, String target_host, int target_port, byte[] bytes, int connect_timeout, int read_timeout, boolean unconnected_socket_hack )1675 connectSocketAndWrite( 1676 boolean is_ssl, 1677 String target_host, 1678 int target_port, 1679 byte[] bytes, 1680 int connect_timeout, 1681 int read_timeout, 1682 boolean unconnected_socket_hack ) 1683 1684 throws Exception 1685 { 1686 boolean cert_hack = false; 1687 boolean dh_hack = false; 1688 boolean internal_error_hack = false; 1689 1690 boolean hacks_to_do = true; 1691 Exception last_error = null; 1692 1693 while( hacks_to_do ){ 1694 1695 hacks_to_do = false; 1696 1697 Socket target = null; 1698 1699 boolean ok = false; 1700 1701 try{ 1702 1703 InetSocketAddress targetSockAddress = new InetSocketAddress( InetAddress.getByName(target_host) , target_port ); 1704 1705 InetAddress bindIP = NetworkAdmin.getSingleton().getSingleHomedServiceBindAddress(targetSockAddress.getAddress() instanceof Inet6Address ? NetworkAdmin.IP_PROTOCOL_VERSION_REQUIRE_V6 : NetworkAdmin.IP_PROTOCOL_VERSION_REQUIRE_V4); 1706 1707 if ( is_ssl ){ 1708 1709 TrustManager[] tms_delegate = SESecurityManager.getAllTrustingTrustManager(); 1710 1711 SSLContext sc = SSLContext.getInstance("SSL"); 1712 1713 sc.init( null, tms_delegate, RandomUtils.SECURE_RANDOM ); 1714 1715 SSLSocketFactory factory = sc.getSocketFactory(); 1716 1717 if ( dh_hack ){ 1718 1719 factory = DHHackIt( factory ); 1720 }else{ 1721 1722 factory = DHHackIt( factory ); 1723 1724 } 1725 1726 if ( unconnected_socket_hack ){ 1727 1728 if ( bindIP == null ){ 1729 1730 target = factory.createSocket(targetSockAddress.getAddress(), targetSockAddress.getPort()); 1731 1732 }else{ 1733 1734 target = factory.createSocket(targetSockAddress.getAddress(), targetSockAddress.getPort(), bindIP, 0 ); 1735 } 1736 }else{ 1737 1738 target = factory.createSocket(); 1739 } 1740 }else{ 1741 1742 if ( unconnected_socket_hack ){ 1743 1744 if ( bindIP == null ){ 1745 1746 target = new Socket(targetSockAddress.getAddress(), targetSockAddress.getPort()); 1747 1748 }else{ 1749 1750 target = new Socket(targetSockAddress.getAddress(), targetSockAddress.getPort(), bindIP, 0 ); 1751 } 1752 }else{ 1753 1754 target = new Socket(); 1755 } 1756 } 1757 1758 if ( internal_error_hack ){ 1759 1760 SSLSocketSNIHack( target_host, (SSLSocket)target ); 1761 } 1762 1763 target.setSoTimeout( read_timeout ); 1764 1765 if ( !unconnected_socket_hack ){ 1766 1767 if ( bindIP != null ){ 1768 1769 target.bind( new InetSocketAddress( bindIP, 0 ) ); 1770 } 1771 1772 target.connect( targetSockAddress, connect_timeout ); 1773 } 1774 1775 target.getOutputStream().write( bytes ); 1776 1777 ok = true; 1778 1779 return( target ); 1780 1781 }catch( Exception e ){ 1782 1783 last_error = e; 1784 1785 if ( e instanceof SSLException ){ 1786 1787 String msg = Debug.getNestedExceptionMessage( e ); 1788 1789 if ( msg.contains( "DH keypair" )){ 1790 1791 if ( !dh_hack ){ 1792 1793 dh_hack = true; 1794 1795 hacks_to_do = true; 1796 } 1797 }else if ( msg.contains( "internal_error" )){ 1798 1799 if ( !internal_error_hack ){ 1800 1801 internal_error_hack = true; 1802 1803 hacks_to_do = true; 1804 } 1805 } 1806 1807 if ( !cert_hack ){ 1808 1809 cert_hack = true; 1810 1811 SESecurityManager.installServerCertificates( new URL( "https://" + target_host + ":" + target_port + "/" )); 1812 1813 hacks_to_do = true; 1814 } 1815 } 1816 1817 if ( !hacks_to_do ){ 1818 1819 throw( e ); 1820 } 1821 }finally{ 1822 1823 if ( !ok ){ 1824 1825 if ( target != null ){ 1826 1827 target.close(); 1828 } 1829 } 1830 } 1831 } 1832 1833 throw( last_error ); 1834 } 1835 1836 main(String[] args)1837 public static void main(String[] args) { 1838 1839 //MagnetURIHandler.getSingleton(); 1840 1841 System.out.println( URLEncoder.encode( "http://a.b.c/fred?a=10&b=20")); 1842 1843 byte[] infohash = ByteFormatter.decodeString("1234567890123456789012345678901234567890"); 1844 String[] test = { 1845 "http://moo.com", 1846 "http%3A%2F/moo%2Ecom", 1847 "magnet:?moo", 1848 "magnet%3A%3Fxt=urn:btih:26", 1849 "magnet%3A//%3Fmooo", 1850 "magnet:?xt=urn:btih:" + Base32.encode(infohash), 1851 "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", 1852 "magnet:?dn=OpenOffice.org_2.0.3_Win32Intel_install.exe&xt=urn:sha1:PEMIGLKMNFI4HZ4CCHZNPKZJNMAAORKN&xt=urn:tree:tiger:JMIJVWHCQUX47YYH7O4XIBCORNU2KYKHBBC6DHA&xt=urn:ed2k:1c0804541f34b6583a383bb8f2cec682&xl=96793015&xs=http://mirror.switch.ch/ftp/mirror/OpenOffice/stable/2.0.3/OOo_2.0.3_Win32Intel_install.exe%3Fa%3D10%26b%3D20", 1853 }; 1854 for (int i = 0; i < test.length; i++) { 1855 System.out.println( test[i] ); 1856 System.out.println("URLDecoder.decode: -> " + URLDecoder.decode(test[i])); 1857 System.out.println("decode: -> " + decode(test[i])); 1858 System.out.println("decodeIf: -> " + decodeIfNeeded(test[i])); 1859 System.out.println("isURL: -> " + isURL(test[i])); 1860 System.out.println("parse: -> " + parseTextForURL(test[i], true)); 1861 } 1862 1863 String[] testEncode = { 1864 "a b" 1865 }; 1866 for (int i = 0; i < testEncode.length; i++) { 1867 String txt = testEncode[i]; 1868 try { 1869 System.out.println("URLEncoder.encode: " + txt + " -> " 1870 + URLEncoder.encode(txt, "UTF8")); 1871 } catch (UnsupportedEncodingException e) { 1872 } 1873 System.out.println("URLEncoder.encode: " + txt + " -> " 1874 + URLEncoder.encode(txt)); 1875 System.out.println("encode: " + txt + " -> " + encode(txt)); 1876 } 1877 1878 } 1879 } 1880