xref: /aosp_15_r20/external/gturri-aXMLRPC/src/main/java/de/timroes/axmlrpc/XMLRPCClient.java (revision 1b3e0c610889c330a4f530b7731f3b672f65ccad)
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 & and the "less then" sign to <.
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