1 package de.timroes.axmlrpc; 2 3 import de.timroes.axmlrpc.serializer.SerializerHandler; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.OutputStreamWriter; 7 import java.net.*; 8 import java.security.SecureRandom; 9 import java.security.cert.CertificateException; 10 import java.security.cert.X509Certificate; 11 import java.util.Map; 12 import java.util.Properties; 13 import java.util.concurrent.ConcurrentHashMap; 14 import javax.net.ssl.*; 15 16 /** 17 * An XMLRPCClient is a client used to make XML-RPC (Extensible Markup Language 18 * Remote Procedure Calls). 19 * The specification of XMLRPC can be found at http://www.xmlrpc.com/spec. 20 * You can use flags to extend the functionality of the client to some extras. 21 * Further information on the flags can be found in the documentation of these. 22 * For a documentation on how to use this class see also the README file delivered 23 * with the source of this library. 24 * 25 * @author Tim Roes 26 */ 27 public class XMLRPCClient { 28 29 private static final String DEFAULT_USER_AGENT = "aXMLRPC"; 30 31 /** 32 * Constants from the http protocol. 33 */ 34 static final String USER_AGENT = "User-Agent"; 35 static final String CONTENT_TYPE = "Content-Type"; 36 static final String TYPE_XML = "text/xml; charset=utf-8"; 37 static final String HOST = "Host"; 38 static final String CONTENT_LENGTH = "Content-Length"; 39 static final String HTTP_POST = "POST"; 40 41 /** 42 * XML elements to be used. 43 */ 44 static final String METHOD_RESPONSE = "methodResponse"; 45 static final String PARAMS = "params"; 46 static final String PARAM = "param"; 47 public static final String VALUE = "value"; 48 static final String FAULT = "fault"; 49 static final String METHOD_CALL = "methodCall"; 50 static final String METHOD_NAME = "methodName"; 51 static final String STRUCT_MEMBER = "member"; 52 53 /** 54 * No flags should be set. 55 */ 56 public static final int FLAGS_NONE = 0x0; 57 58 /** 59 * The client should parse responses strict to specification. 60 * It will check if the given content-type is right. 61 * The method name in a call must only contain of A-Z, a-z, 0-9, _, ., :, / 62 * Normally this is not needed. 63 */ 64 public static final int FLAGS_STRICT = 0x01; 65 66 /** 67 * The client will be able to handle 8 byte integer values (longs). 68 * The xml type tag <i8> will be used. This is not in the specification 69 * but some libraries and servers support this behaviour. 70 * If this isn't enabled you cannot recieve 8 byte integers and if you try to 71 * send a long the value must be within the 4byte integer range. 72 */ 73 public static final int FLAGS_8BYTE_INT = 0x02; 74 75 /** 76 * With this flag, the client will be able to handle cookies, meaning saving cookies 77 * from the server and sending it with every other request again. This is needed 78 * for some XML-RPC interfaces that support login. 79 */ 80 public static final int FLAGS_ENABLE_COOKIES = 0x04; 81 82 /** 83 * The client will be able to send null values. A null value will be send 84 * as <nil/>. This extension is described under: http://ontosys.com/xml-rpc/extensions.php 85 */ 86 public static final int FLAGS_NIL = 0x08; 87 88 /** 89 * With this flag enabled, the XML-RPC client will ignore the HTTP status 90 * code of the response from the server. According to specification the 91 * status code must be 200. This flag is only needed for the use with 92 * not standard compliant servers. 93 */ 94 public static final int FLAGS_IGNORE_STATUSCODE = 0x10; 95 96 /** 97 * With this flag enabled, the client will forward the request, if 98 * the 301 or 302 HTTP status code has been received. If this flag has not 99 * been set, the client will throw an exception on these HTTP status codes. 100 */ 101 public static final int FLAGS_FORWARD = 0x20; 102 103 /** 104 * With this flag enabled, the client will ignore, if the URL doesn't match 105 * the SSL Certificate. This should be used with caution. Normally the URL 106 * should always match the URL in the SSL certificate, even with self signed 107 * certificates. 108 */ 109 public static final int FLAGS_SSL_IGNORE_INVALID_HOST = 0x40; 110 111 /** 112 * With this flag enabled, the client will ignore all unverified SSL/TLS 113 * certificates. This must be used, if you use self-signed certificates 114 * or certificated from unknown (or untrusted) authorities. If this flag is 115 * used, calls to {@link #installCustomTrustManager(javax.net.ssl.TrustManager)} 116 * won't have any effect. 117 */ 118 public static final int FLAGS_SSL_IGNORE_INVALID_CERT = 0x80; 119 120 /** 121 * With this flag enabled, a value with a missing type tag, will be parsed 122 * as a string element. This is just for incoming messages. Outgoing messages 123 * will still be generated according to specification. 124 */ 125 public static final int FLAGS_DEFAULT_TYPE_STRING = 0x100; 126 127 /** 128 * With this flag enabled, the {@link XMLRPCClient} ignores all namespaces 129 * used within the response from the server. 130 */ 131 public static final int FLAGS_IGNORE_NAMESPACES = 0x200; 132 133 /** 134 * With this flag enabled, the {@link XMLRPCClient} will use the system http 135 * proxy to connect to the XML-RPC server. 136 */ 137 public static final int FLAGS_USE_SYSTEM_PROXY = 0x400; 138 139 /** 140 * This prevents the decoding of incoming strings, meaning & and < 141 * won't be decoded to the & sign and the "less then" sign. See 142 * {@link #FLAGS_NO_STRING_ENCODE} for the counterpart. 143 */ 144 public static final int FLAGS_NO_STRING_DECODE = 0x800; 145 146 /** 147 * By default outgoing string values will be encoded according to specification. 148 * Meaning the & sign will be encoded to &amp; and the "less then" sign to &lt;. 149 * If you set this flag, the encoding won't be done for outgoing string values. 150 * See {@link #FLAGS_NO_STRING_ENCODE} for the counterpart. 151 */ 152 public static final int FLAGS_NO_STRING_ENCODE = 0x1000; 153 154 /** 155 * Activate debug mode. 156 * Do NOT use if you don't need it. 157 */ 158 public static final int FLAGS_DEBUG = 0x2000; 159 160 /** 161 * Accepts response containing eg: <dateTime.iso8601/> 162 */ 163 public static final int FLAGS_ACCEPT_NULL_DATES = 0x4000; 164 165 /** 166 * This flag disables all SSL warnings. It is an alternative to use 167 * FLAGS_SSL_IGNORE_INVALID_CERT | FLAGS_SSL_IGNORE_INVALID_HOST. There 168 * is no functional difference. 169 */ 170 public static final int FLAGS_SSL_IGNORE_ERRORS = 171 FLAGS_SSL_IGNORE_INVALID_CERT | FLAGS_SSL_IGNORE_INVALID_HOST; 172 173 /** 174 * This flag should be used if the server is an apache ws xmlrpc server. 175 * This will set some flags, so that the not standard conform behavior 176 * of the server will be ignored. 177 * This will enable the following flags: FLAGS_IGNORE_NAMESPACES, FLAGS_NIL, 178 * FLAGS_DEFAULT_TYPE_STRING 179 */ 180 public static final int FLAGS_APACHE_WS = FLAGS_IGNORE_NAMESPACES | FLAGS_NIL 181 | FLAGS_DEFAULT_TYPE_STRING; 182 183 private final int flags; 184 185 private URL url; 186 private Map<String,String> httpParameters = new ConcurrentHashMap<String, String>(); 187 188 private Map<Long,Caller> backgroundCalls = new ConcurrentHashMap<Long, Caller>(); 189 190 private ResponseParser responseParser; 191 private CookieManager cookieManager; 192 private AuthenticationManager authManager; 193 194 private TrustManager[] trustManagers; 195 private KeyManager[] keyManagers; 196 197 private Proxy proxy; 198 199 private int timeout; 200 private final SerializerHandler serializerHandler; 201 202 /** 203 * Create a new XMLRPC client for the given URL. 204 * 205 * @param url The URL to send the requests to. 206 * @param userAgent A user agent string to use in the HTTP requests. 207 * @param flags A combination of flags to be set. 208 */ XMLRPCClient(URL url, String userAgent, int flags)209 public XMLRPCClient(URL url, String userAgent, int flags) { 210 211 this.serializerHandler = new SerializerHandler(flags); 212 213 this.url = url; 214 215 this.flags = flags; 216 // Create a parser for the http responses. 217 responseParser = new ResponseParser(); 218 219 cookieManager = new CookieManager(flags); 220 authManager = new AuthenticationManager(); 221 222 httpParameters.put(CONTENT_TYPE, TYPE_XML); 223 httpParameters.put(USER_AGENT, userAgent); 224 225 // If invalid ssl certs are ignored, instantiate an all trusting TrustManager 226 if(isFlagSet(FLAGS_SSL_IGNORE_INVALID_CERT)) { 227 trustManagers = new TrustManager[] { 228 new X509TrustManager() { 229 public void checkClientTrusted(X509Certificate[] xcs, String string) 230 throws CertificateException { } 231 232 public void checkServerTrusted(X509Certificate[] xcs, String string) 233 throws CertificateException { } 234 235 public X509Certificate[] getAcceptedIssuers() { 236 return null; 237 } 238 } 239 }; 240 } 241 242 if(isFlagSet(FLAGS_USE_SYSTEM_PROXY)) { 243 // Read system proxy settings and generate a proxy from that 244 Properties prop = System.getProperties(); 245 String proxyHost = prop.getProperty("http.proxyHost"); 246 int proxyPort = Integer.parseInt(prop.getProperty("http.proxyPort", "0")); 247 if(proxyPort > 0 && proxyHost.length() > 0 && !proxyHost.equals("null")) { 248 proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); 249 } 250 } 251 252 } 253 254 /** 255 * Create a new XMLRPC client for the given URL. 256 * The default user agent string will be used. 257 * 258 * @param url The URL to send the requests to. 259 * @param flags A combination of flags to be set. 260 */ XMLRPCClient(URL url, int flags)261 public XMLRPCClient(URL url, int flags) { 262 this(url, DEFAULT_USER_AGENT, flags); 263 } 264 265 /** 266 * Create a new XMLRPC client for the given url. 267 * No flags will be set. 268 * 269 * @param url The url to send the requests to. 270 * @param userAgent A user agent string to use in the http request. 271 */ XMLRPCClient(URL url, String userAgent)272 public XMLRPCClient(URL url, String userAgent) { 273 this(url, userAgent, FLAGS_NONE); 274 } 275 276 /** 277 * Create a new XMLRPC client for the given url. 278 * No flags will be used. 279 * The default user agent string will be used. 280 * 281 * @param url The url to send the requests to. 282 */ XMLRPCClient(URL url)283 public XMLRPCClient(URL url) { 284 this(url, DEFAULT_USER_AGENT, FLAGS_NONE); 285 } 286 287 /** 288 * Returns the URL this XMLRPCClient is connected to. If that URL permanently forwards 289 * to another URL, this method will return the forwarded URL, as soon as 290 * the first call has been made. 291 * 292 * @return Returns the URL for this XMLRPCClient. 293 */ getURL()294 public URL getURL() { 295 return url; 296 } 297 298 /** 299 * Sets the time in seconds after which a call should timeout. 300 * If {@code timeout} will be zero or less the connection will never timeout. 301 * In case the connection times out an {@link XMLRPCTimeoutException} will 302 * be thrown for calls made by {@link #call(java.lang.String, java.lang.Object[])}. 303 * For calls made by {@link #callAsync(de.timroes.axmlrpc.XMLRPCCallback, java.lang.String, java.lang.Object[])} 304 * the {@link XMLRPCCallback#onError(long, de.timroes.axmlrpc.XMLRPCException)} method 305 * of the callback will be called. By default connections won't timeout. 306 * 307 * @param timeout The timeout for connections in seconds. 308 */ setTimeout(int timeout)309 public void setTimeout(int timeout) { 310 this.timeout = timeout; 311 } 312 313 /** 314 * Sets the user agent string. 315 * If this method is never called the default 316 * user agent 'aXMLRPC' will be used. 317 * 318 * @param userAgent The new user agent string. 319 */ setUserAgentString(String userAgent)320 public void setUserAgentString(String userAgent) { 321 httpParameters.put(USER_AGENT, userAgent); 322 } 323 324 /** 325 * Sets a proxy to use for this client. If you want to use the system proxy, 326 * use {@link #FLAGS_USE_SYSTEM_PROXY} instead. If combined with 327 * {@code FLAGS_USE_SYSTEM_PROXY}, this proxy will be used instead of the 328 * system proxy. 329 * 330 * @param proxy A proxy to use for the connection. 331 */ setProxy(Proxy proxy)332 public void setProxy(Proxy proxy) { 333 this.proxy = proxy; 334 } 335 336 /** 337 * Set a HTTP header field to a custom value. 338 * You cannot modify the Host or Content-Type field that way. 339 * If the field already exists, the old value is overwritten. 340 * 341 * @param headerName The name of the header field. 342 * @param headerValue The new value of the header field. 343 */ setCustomHttpHeader(String headerName, String headerValue)344 public void setCustomHttpHeader(String headerName, String headerValue) { 345 if(CONTENT_TYPE.equals(headerName) || HOST.equals(headerName) 346 || CONTENT_LENGTH.equals(headerName)) { 347 throw new XMLRPCRuntimeException("You cannot modify the Host, Content-Type or Content-Length header."); 348 } 349 httpParameters.put(headerName, headerValue); 350 } 351 352 /** 353 * Set the username and password that should be used to perform basic 354 * http authentication. 355 * 356 * @param user Username 357 * @param pass Password 358 */ setLoginData(String user, String pass)359 public void setLoginData(String user, String pass) { 360 authManager.setAuthData(user, pass); 361 } 362 363 /** 364 * Clear the username and password. No basic HTTP authentication will be used 365 * in the next calls. 366 */ clearLoginData()367 public void clearLoginData() { 368 authManager.clearAuthData(); 369 } 370 371 /** 372 * Returns a {@link Map} of all cookies. It contains each cookie key as a map 373 * key and its value as a map value. Cookies will only be used if {@link #FLAGS_ENABLE_COOKIES} 374 * has been set for the client. This map will also be available (and empty) 375 * when this flag hasn't been said, but has no effect on the HTTP connection. 376 * 377 * @return A {@code Map} of all cookies. 378 */ getCookies()379 public Map<String,String> getCookies() { 380 return cookieManager.getCookies(); 381 } 382 383 /** 384 * Delete all cookies currently used by the client. 385 * This method has only an effect, as long as the FLAGS_ENABLE_COOKIES has 386 * been set on this client. 387 */ clearCookies()388 public void clearCookies() { 389 cookieManager.clearCookies(); 390 } 391 392 /** 393 * Installs a custom {@link TrustManager} to handle SSL/TLS certificate verification. 394 * This will replace any previously installed {@code TrustManager}s. 395 * If {@link #FLAGS_SSL_IGNORE_INVALID_CERT} is set, this won't do anything. 396 * 397 * @param trustManager {@link TrustManager} to install. 398 * 399 * @see #installCustomTrustManagers(javax.net.ssl.TrustManager[]) 400 */ installCustomTrustManager(TrustManager trustManager)401 public void installCustomTrustManager(TrustManager trustManager) { 402 if(!isFlagSet(FLAGS_SSL_IGNORE_INVALID_CERT)) { 403 trustManagers = new TrustManager[] { trustManager }; 404 } 405 } 406 407 /** 408 * Installs custom {@link TrustManager TrustManagers} to handle SSL/TLS certificate 409 * verification. This will replace any previously installed {@code TrustManagers}s. 410 * If {@link #FLAGS_SSL_IGNORE_INVALID_CERT} is set, this won't do anything. 411 * 412 * @param trustManagers {@link TrustManager TrustManagers} to install. 413 * 414 * @see #installCustomTrustManager(javax.net.ssl.TrustManager) 415 */ installCustomTrustManagers(TrustManager[] trustManagers)416 public void installCustomTrustManagers(TrustManager[] trustManagers) { 417 if(!isFlagSet(FLAGS_SSL_IGNORE_INVALID_CERT)) { 418 this.trustManagers = trustManagers.clone(); 419 } 420 } 421 422 /** 423 * Installs a custom {@link KeyManager} to handle SSL/TLS certificate verification. 424 * This will replace any previously installed {@code KeyManager}s. 425 * If {@link #FLAGS_SSL_IGNORE_INVALID_CERT} is set, this won't do anything. 426 * 427 * @param keyManager {@link KeyManager} to install. 428 * 429 * @see #installCustomKeyManagers(javax.net.ssl.KeyManager[]) 430 */ installCustomKeyManager(KeyManager keyManager)431 public void installCustomKeyManager(KeyManager keyManager) { 432 if(!isFlagSet(FLAGS_SSL_IGNORE_INVALID_CERT)) { 433 keyManagers = new KeyManager[] { keyManager }; 434 } 435 } 436 437 /** 438 * Installs custom {@link KeyManager KeyManagers} to handle SSL/TLS certificate 439 * verification. This will replace any previously installed {@code KeyManagers}s. 440 * If {@link #FLAGS_SSL_IGNORE_INVALID_CERT} is set, this won't do anything. 441 * 442 * @param keyManagers {@link KeyManager KeyManagers} to install. 443 * 444 * @see #installCustomKeyManager(javax.net.ssl.KeyManager) 445 */ installCustomKeyManagers(KeyManager[] keyManagers)446 public void installCustomKeyManagers(KeyManager[] keyManagers) { 447 if(!isFlagSet(FLAGS_SSL_IGNORE_INVALID_CERT)) { 448 this.keyManagers = keyManagers.clone(); 449 } 450 } 451 452 /** 453 * Call a remote procedure on the server. The method must be described by 454 * a method name. If the method requires parameters, this must be set. 455 * The type of the return object depends on the server. You should consult 456 * the server documentation and then cast the return value according to that. 457 * This method will block until the server returned a result (or an error occurred). 458 * Read the README file delivered with the source code of this library for more 459 * information. 460 * 461 * @param method A method name to call. 462 * @param params An array of parameters for the method. 463 * @return The result of the server. 464 * @throws XMLRPCException Will be thrown if an error occurred during the call. 465 */ call(String method, Object... params)466 public Object call(String method, Object... params) throws XMLRPCException { 467 return new Caller().call(method, params); 468 } 469 470 /** 471 * Asynchronously call a remote procedure on the server. The method must be 472 * described by a method name. If the method requires parameters, this must 473 * be set. When the server returns a response the onResponse method is called 474 * on the listener. If the server returns an error the onServerError method 475 * is called on the listener. The onError method is called whenever something 476 * fails. This method returns immediately and returns an identifier for the 477 * request. All listener methods get this id as a parameter to distinguish between 478 * multiple requests. 479 * 480 * @param listener A listener, which will be notified about the server response or errors. 481 * @param methodName A method name to call on the server. 482 * @param params An array of parameters for the method. 483 * @return The id of the current request. 484 */ callAsync(XMLRPCCallback listener, String methodName, Object... params)485 public long callAsync(XMLRPCCallback listener, String methodName, Object... params) { 486 long id = System.currentTimeMillis(); 487 new Caller(listener, id, methodName, params).start(); 488 return id; 489 } 490 491 /** 492 * Cancel a specific asynchronous call. 493 * 494 * @param id The id of the call as returned by the callAsync method. 495 */ cancel(long id)496 public void cancel(long id) { 497 498 // Lookup the background call for the given id. 499 Caller cancel = backgroundCalls.get(id); 500 if(cancel == null) { 501 return; 502 } 503 504 // Cancel the thread 505 cancel.cancel(); 506 507 try { 508 // Wait for the thread 509 cancel.join(); 510 } catch (InterruptedException ex) { 511 // Ignore this 512 } 513 514 } 515 516 /** 517 * Create a call object from a given method string and parameters. 518 * 519 * @param method The method that should be called. 520 * @param params An array of parameters or null if no parameters needed. 521 * @return A call object. 522 */ createCall(String method, Object[] params)523 private Call createCall(String method, Object[] params) { 524 525 if(isFlagSet(FLAGS_STRICT) && !method.matches("^[A-Za-z0-9\\._:/]*$")) { 526 throw new XMLRPCRuntimeException("Method name must only contain A-Z a-z . : _ / "); 527 } 528 529 return new Call(serializerHandler, method, params); 530 } 531 532 /** 533 * Checks whether a specific flag has been set. 534 * 535 * @param flag The flag to check for. 536 * @return Whether the flag has been set. 537 */ isFlagSet(int flag)538 private boolean isFlagSet(int flag) { 539 return (this.flags & flag) != 0; 540 } 541 542 /** 543 * The Caller class is used to make asynchronous calls to the server. 544 * For synchronous calls the Thread function of this class isn't used. 545 */ 546 private class Caller extends Thread { 547 548 private XMLRPCCallback listener; 549 private long threadId; 550 private String methodName; 551 private Object[] params; 552 553 private volatile boolean canceled; 554 private HttpURLConnection http; 555 556 /** 557 * Create a new Caller for asynchronous use. 558 * 559 * @param listener The listener to notice about the response or an error. 560 * @param threadId An id that will be send to the listener. 561 * @param methodName The method name to call. 562 * @param params The parameters of the call or null. 563 */ Caller(XMLRPCCallback listener, long threadId, String methodName, Object[] params)564 public Caller(XMLRPCCallback listener, long threadId, String methodName, Object[] params) { 565 this.listener = listener; 566 this.threadId = threadId; 567 this.methodName = methodName; 568 this.params = params; 569 } 570 571 /** 572 * Create a new Caller for synchronous use. 573 * If the caller has been created with this constructor you cannot use the 574 * start method to start it as a thread. But you can call the call method 575 * on it for synchronous use. 576 */ Caller()577 public Caller() { } 578 579 /** 580 * The run method is invoked when the thread gets started. 581 * This will only work, if the Caller has been created with parameters. 582 * It execute the call method and notify the listener about the result. 583 */ 584 @Override run()585 public void run() { 586 587 if(listener == null) 588 return; 589 590 try { 591 backgroundCalls.put(threadId, this); 592 Object o = this.call(methodName, params); 593 listener.onResponse(threadId, o); 594 } catch(CancelException ex) { 595 // Don't notify the listener, if the call has been canceled. 596 } catch(XMLRPCServerException ex) { 597 listener.onServerError(threadId, ex); 598 } catch (XMLRPCException ex) { 599 listener.onError(threadId, ex); 600 } finally { 601 backgroundCalls.remove(threadId); 602 } 603 604 } 605 606 /** 607 * Cancel this call. This will abort the network communication. 608 */ cancel()609 public void cancel() { 610 // Set the flag, that this thread has been canceled 611 canceled = true; 612 // Disconnect the connection to the server 613 http.disconnect(); 614 } 615 616 /** 617 * Call a remote procedure on the server. The method must be described by 618 * a method name. If the method requires parameters, this must be set. 619 * The type of the return object depends on the server. You should consult 620 * the server documentation and then cast the return value according to that. 621 * This method will block until the server returned a result (or an error occurred). 622 * Read the README file delivered with the source code of this library for more 623 * information. 624 * 625 * @param methodName A method name to call. 626 * @param params An array of parameters for the method. 627 * @return The result of the server. 628 * @throws XMLRPCException Will be thrown if an error occurred during the call. 629 */ call(String methodName, Object[] params)630 public Object call(String methodName, Object[] params) throws XMLRPCException { 631 632 try { 633 634 Call c = createCall(methodName, params); 635 636 // If proxy is available, use it 637 URLConnection conn; 638 if(proxy != null) 639 conn = url.openConnection(proxy); 640 else 641 conn = url.openConnection(); 642 643 http = verifyConnection(conn); 644 http.setInstanceFollowRedirects(false); 645 http.setRequestMethod(HTTP_POST); 646 http.setDoOutput(true); 647 http.setDoInput(true); 648 649 // Set timeout 650 if(timeout > 0) { 651 http.setConnectTimeout(timeout * 1000); 652 http.setReadTimeout(timeout * 1000); 653 } 654 655 // Set the request parameters 656 for(Map.Entry<String,String> param : httpParameters.entrySet()) { 657 http.setRequestProperty(param.getKey(), param.getValue()); 658 } 659 660 authManager.setAuthentication(http); 661 cookieManager.setCookies(http); 662 663 OutputStreamWriter stream = new OutputStreamWriter(http.getOutputStream(), "UTF-8"); 664 stream.write(c.getXML(isFlagSet(FLAGS_DEBUG))); 665 stream.flush(); 666 stream.close(); 667 668 // Try to get the status code from the connection 669 int statusCode; 670 try { 671 statusCode = http.getResponseCode(); 672 } catch(IOException ex) { 673 // Due to a bug on android, the getResponseCode()-method will 674 // fail the first time, with a IOException, when 401 or 403 has been returned. 675 // The second time it should success. If it fail the second time again 676 // the normal exception handling can take care of this, since 677 // it is a real error. 678 statusCode = http.getResponseCode(); 679 } 680 681 InputStream istream; 682 683 // If status code was 401 or 403 throw exception or if appropriate 684 // flag is set, ignore error code. 685 if(statusCode == HttpURLConnection.HTTP_FORBIDDEN 686 || statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) { 687 688 if(isFlagSet(FLAGS_IGNORE_STATUSCODE)) { 689 // getInputStream will fail if server returned above 690 // error code, use getErrorStream instead 691 istream = http.getErrorStream(); 692 } else { 693 throw new XMLRPCException("Invalid status code '" 694 + statusCode + "' returned from server."); 695 } 696 697 } else { 698 istream = http.getInputStream(); 699 } 700 701 // If status code is 301 Moved Permanently or 302 Found ... 702 if(statusCode == HttpURLConnection.HTTP_MOVED_PERM 703 || statusCode == HttpURLConnection.HTTP_MOVED_TEMP) { 704 // ... do either a foward 705 if(isFlagSet(FLAGS_FORWARD)) { 706 boolean temporaryForward = statusCode == HttpURLConnection.HTTP_MOVED_TEMP; 707 708 // Get new location from header field. 709 String newLocation = http.getHeaderField("Location"); 710 // Try getting header in lower case, if no header has been found 711 if(newLocation == null || newLocation.length() <= 0) 712 newLocation = http.getHeaderField("location"); 713 714 // Set new location, disconnect current connection and request to new location. 715 URL oldURL = url; 716 url = new URL(newLocation); 717 http.disconnect(); 718 Object forwardedResult = call(methodName, params); 719 720 // In case of temporary forward, restore original URL again for next call. 721 if(temporaryForward) { 722 url = oldURL; 723 } 724 725 return forwardedResult; 726 727 } else { 728 // ... or throw an exception 729 throw new XMLRPCException("The server responded with a http 301 or 302 status " 730 + "code, but forwarding has not been enabled (FLAGS_FORWARD)."); 731 732 } 733 } 734 735 if(!isFlagSet(FLAGS_IGNORE_STATUSCODE) 736 && statusCode != HttpURLConnection.HTTP_OK) { 737 throw new XMLRPCException("The status code of the http response must be 200."); 738 } 739 740 // Check for strict parameters 741 if(isFlagSet(FLAGS_STRICT) && !http.getContentType().startsWith(TYPE_XML)) { 742 throw new XMLRPCException("The Content-Type of the response must be text/xml."); 743 } 744 745 cookieManager.readCookies(http); 746 747 return responseParser.parse(serializerHandler, istream, isFlagSet(FLAGS_DEBUG)); 748 749 } catch(SocketTimeoutException ex) { 750 throw new XMLRPCTimeoutException("The XMLRPC call timed out."); 751 } catch (IOException ex) { 752 // If the thread has been canceled this exception will be thrown. 753 // So only throw an exception if the thread hasnt been canceled 754 // or if the thred has not been started in background. 755 if(!canceled || threadId <= 0) { 756 throw new XMLRPCException(ex); 757 } else { 758 throw new CancelException(); 759 } 760 } 761 762 } 763 764 /** 765 * Verifies the given URLConnection to be a valid HTTP or HTTPS connection. 766 * If the SSL ignoring flags are set, the method will ignore SSL warnings. 767 * 768 * @param conn The URLConnection to validate. 769 * @return The verified HttpURLConnection. 770 * @throws XMLRPCException Will be thrown if an error occurred. 771 */ verifyConnection(URLConnection conn)772 private HttpURLConnection verifyConnection(URLConnection conn) throws XMLRPCException { 773 774 if(!(conn instanceof HttpURLConnection)) { 775 throw new IllegalArgumentException("The URL is not valid for a http connection."); 776 } 777 778 // Validate the connection if its an SSL connection 779 if(conn instanceof HttpsURLConnection) { 780 781 HttpsURLConnection h = (HttpsURLConnection)conn; 782 783 // Don't check, that URL matches the certificate. 784 if(isFlagSet(FLAGS_SSL_IGNORE_INVALID_HOST)) { 785 h.setHostnameVerifier(new HostnameVerifier() { 786 public boolean verify(String host, SSLSession ssl) { 787 return true; 788 } 789 }); 790 } 791 792 // Associate the TrustManager with TLS and SSL connections, if present. 793 if(trustManagers != null) { 794 try { 795 String[] sslContexts = new String[]{ "TLS", "SSL" }; 796 797 for(String ctx : sslContexts) { 798 SSLContext sc = SSLContext.getInstance(ctx); 799 sc.init(keyManagers, trustManagers, new SecureRandom()); 800 h.setSSLSocketFactory(sc.getSocketFactory()); 801 } 802 803 } catch(Exception ex) { 804 throw new XMLRPCException(ex); 805 } 806 807 } 808 809 return h; 810 811 } 812 813 return (HttpURLConnection)conn; 814 815 } 816 817 } 818 819 private class CancelException extends RuntimeException { } 820 821 } 822