/**********************************************************\
| |
| The implementation of PHPRPC Protocol 3.0 |
| |
| PHPRPC_Client.java |
| |
| Release 3.0.1 |
| Copyright (c) 2005-2008 by Team-PHPRPC |
| |
| WebSite: http://www.phprpc.org/ |
| http://www.phprpc.net/ |
| http://www.phprpc.com/ |
| http://sourceforge.net/projects/php-rpc/ |
| |
| Authors: Ma Bingyao <andot@ujn.edu.cn> |
| |
| This file may be distributed and/or modified under the |
| terms of the GNU Lesser General Public License (LGPL) |
| version 3.0 as published by the Free Software Foundation |
| and appearing in the included file LICENSE. |
| |
\**********************************************************/
/* PHPRPC_Client class.
*
* Copyright (C) 2005-2008 Ma Bingyao <andot@ujn.edu.cn>
* Version: 3.0.1
* LastModified: Sep 18, 2008
* This library is free. You can redistribute it and/or modify it.
*
/*
* Example usage:
*
import org.phprpc.PHPRPC_Client;
interface remoteFunctions {
public int add(int a, int b);
public double add(double a, double b);
public String add(String a, String b);
}
public class SinTest
{
public static void main(String[] args) {
PHPRPC_Client rpc = new PHPRPC_Client("http://www.phprpc.org/server.php");
remoteFunctions rf = (remoteFunctions)rpc.useService(remoteFunctions.class);
rpc.setKeyLength(1024);
rpc.setEncryptMode(2);
System.out.println(rf.add(1, 2));
System.out.println(rf.add(1.5, 2.6));
System.out.println(rf.add("1", "2"));
}
}
*
*/
package org.phprpc;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import java.util.zip.GZIPInputStream;
import java.math.BigInteger;
import java.net.URL;
import java.net.Socket;
import java.net.MalformedURLException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import org.phprpc.util.Base64;
import org.phprpc.util.XXTEA;
import org.phprpc.util.Cast;
import org.phprpc.util.PHPSerializer;
final class PHPRPC_InvocationHandler implements InvocationHandler {
private PHPRPC_Client rpc;
PHPRPC_InvocationHandler(PHPRPC_Client rpc) {
this.rpc = rpc;
}
public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = rpc.invoke(method.getName(), args);
if (result instanceof PHPRPC_Error) {
throw (PHPRPC_Error)result;
}
return Cast.cast(result, method.getReturnType(), rpc.getCharset());
}
}
public class PHPRPC_Client {
protected PHPSerializer __phpser = null;
private HashMap __server = null;
private HashMap __proxy = null;
private int __timeout = 30000;
private PHPRPC_Error __warning = null;
private byte[] __key = null;
private int __keylen = 128;
private int __encryptMode = 0;
private HashMap __cookies = new HashMap();
private String __cookie = null;
private String __charset = "utf-8";
private String __output = "";
private Socket __socket = null;
private boolean __keepAlive = true;
public PHPRPC_Client() {
}
public PHPRPC_Client(String serverURL) {
useService(serverURL);
}
public final Object useService(Class type) {
PHPRPC_InvocationHandler handler = new PHPRPC_InvocationHandler(this);
if (type.isInterface()) {
return Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, handler);
}
else {
return Proxy.newProxyInstance(type.getClassLoader(), type.getInterfaces(), handler);
}
}
public final Object useService(Class[] interfaces) {
PHPRPC_InvocationHandler handler = new PHPRPC_InvocationHandler(this);
return Proxy.newProxyInstance(interfaces[0].getClassLoader(), interfaces, handler);
}
public final boolean useService(String serverURL) {
return useService(serverURL, null, null);
}
public final Object useService(String serverURL, Class type) {
if (useService(serverURL, null, null)) {
return useService(type);
}
else {
return null;
}
}
public final Object useService(String serverURL, Class[] interfaces) {
if (useService(serverURL, null, null)) {
return useService(interfaces);
}
else {
return null;
}
}
public final boolean useService(String serverURL, String username, String password) {
URL url;
try {
url = new URL(serverURL);
}
catch (MalformedURLException e) {
return false;
}
if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) {
return false;
}
__server = new HashMap();
__server.put("scheme", url.getProtocol());
__server.put("host", url.getHost());
__server.put("port", new Integer((url.getPort() == -1) ? url.getDefaultPort() : url.getPort()));
__server.put("path", url.getFile());
__server.put("userinfo", ((username == null) ? url.getUserInfo() : username + ':' + password));
__disconnect();
__keepAlive = true;
__key = null;
__keylen = 128;
__encryptMode = 0;
__cookies = new HashMap();
__cookie = null;
__phpser = new PHPSerializer();
setCharset("utf-8");
return true;
}
public final Object useService(String serverURL, String username, String password, Class type) {
if (useService(serverURL, username, password)) {
return useService(type);
}
else {
return null;
}
}
public final Object useService(String serverURL, String username, String password, Class[] interfaces) {
if (useService(serverURL, username, password)) {
return useService(interfaces);
}
else {
return null;
}
}
public final void setProxy(String address) throws MalformedURLException {
if (address == null) {
__proxy = null;
}
else {
URL url = new URL(address);
setProxy(url.getHost(), ((url.getPort() == -1) ? url.getDefaultPort() : url.getPort()), url.getUserInfo());
}
}
public final void setProxy(String host, int port) {
setProxy(host, port, null);
}
public final void setProxy(String host, int port, String username, String password) {
setProxy(host, port, ((username == null) ? null : username + ':' + password));
}
private final void setProxy(String host, int port, String userinfo) {
__proxy = new HashMap();
__proxy.put("host", host);
__proxy.put("port", new Integer(port));
__proxy.put("userinfo", userinfo);
}
public final boolean setKeyLength(int keyLength) {
if (__key != null) {
return false;
}
else {
__keylen = keyLength;
return true;
}
}
public final int getKeyLength() {
return __keylen;
}
public final boolean setEncryptMode(int encryptMode) {
if ((encryptMode >= 0) && (encryptMode <= 3)) {
__encryptMode = encryptMode;
return true;
}
else {
__encryptMode = 0;
return false;
}
}
public final int getEncryptMode() {
return __encryptMode;
}
public final void setCharset(String charset) {
__charset = charset;
__phpser.setCharset(__charset);
}
public final String getCharset() {
return __charset;
}
public final void setTimeout(int timeout) {
__timeout = timeout;
}
public final int getTimeout() {
return __timeout;
}
public final String getOutput() {
return __output;
}
public final PHPRPC_Error getWarning() {
return __warning;
}
public final Object invoke(String function, Object[] args) {
return invoke(function, args, false);
}
public final Object invoke(String function, Object[] args, boolean byRef) {
try {
__keyExchange();
StringBuffer requestBody = new StringBuffer();
requestBody.append("phprpc_func=").append(function);
if (args != null && args.length > 0) {
requestBody.append("&phprpc_args=");
requestBody.append(Base64.encode(__encrypt(__phpser.serialize(args), 1)).replaceAll("\\+", "%2B"));
}
requestBody.append("&phprpc_encrypt=").append(__encryptMode);
if (!byRef) {
requestBody.append("&phprpc_ref=false");
}
HashMap result = __post(requestBody.toString());
int errno = Integer.parseInt((String) result.get("phprpc_errno"));
if (errno > 0) {
String errstr = new String(Base64.decode((String) result.get("phprpc_errstr")), __charset);
__warning = new PHPRPC_Error(errno, errstr);
}
else {
__warning = null;
}
if (result.containsKey("phprpc_output")) {
byte[] output = Base64.decode((String) result.get("phprpc_output"));
if (Double.parseDouble((String) __server.get("version")) >= 3) {
output = __decrypt(output, 3);
}
__output = new String(output, __charset);
}
else {
__output = "";
}
if (result.containsKey("phprpc_result")) {
if (result.containsKey("phprpc_args")) {
Object[] arguments = (Object[]) __phpser.unserialize(__decrypt(Base64.decode((String) result.get("phprpc_args")), 1), Object[].class);
for (int i = 0; i < Math.min(args.length, arguments.length); i++) {
args[i] = arguments[i];
}
}
return __phpser.unserialize(__decrypt(Base64.decode((String) result.get("phprpc_result")), 2));
}
else {
return __warning;
}
}
catch (PHPRPC_Error e) {
return e;
}
catch (Throwable e) {
StackTraceElement[] st = e.getStackTrace();
StringBuffer es = new StringBuffer(e.toString()).append("\r\n");
for (int i = 0, n = st.length; i < n; i++) {
es.append(st[i].toString()).append("\r\n");
}
return new PHPRPC_Error(1, es.toString());
}
}
public final void finalize() throws Throwable {
super.finalize();
__disconnect();
}
private final void __connect() throws IOException {
SocketFactory sf;
if (((String) __server.get("scheme")).equals("https")) {
sf = SSLSocketFactory.getDefault();
}
else {
sf = SocketFactory.getDefault();
}
String host;
int port;
if (__proxy != null) {
host = (String) __proxy.get("host");
port = ((Integer) __proxy.get("port")).intValue();
}
else {
host = (String) __server.get("host");
port = ((Integer) __server.get("port")).intValue();
}
__socket = sf.createSocket(host, port);
try {
__socket.setSoTimeout(__timeout);
__socket.setTcpNoDelay(true);
__socket.setKeepAlive(__keepAlive);
}
catch (IOException e) {}
}
private final void __disconnect() {
if (__socket != null) {
try {
__socket.close();
}
catch (IOException e) {}
__socket = null;
}
}
private final void __sendRequest(String requestBody) throws IOException {
StringBuffer url = new StringBuffer();
StringBuffer connection = new StringBuffer();
if (__proxy == null) {
url.append(__server.get("path"));
connection.append("Connection: ");
connection.append(__keepAlive ? "Keep-Alive" : "close");
connection.append("\r\n");
connection.append("Pragma: no-cache\r\n");
connection.append("Cache-Control: no-cache\r\n");
}
else {
url.append(__server.get("scheme"));
url.append("://");
url.append(__server.get("host"));
url.append(":");
url.append(__server.get("port"));
url.append(__server.get("path"));
connection.append("Proxy-Connection: ");
connection.append(__keepAlive ? "Keep-Alive" : "close");
connection.append("\r\n");
if (__proxy.get("userinfo") != null) {
connection.append("Proxy-Authorization: Basic ");
connection.append(Base64.encode(((String) __proxy.get("userinfo")).getBytes(__charset)));
connection.append("\r\n");
}
}
StringBuffer auth = new StringBuffer();
if (__server.get("userinfo") != null) {
auth.append("Authorization: Basic ");
auth.append(Base64.encode(((String) __server.get("userinfo")).getBytes(__charset)));
auth.append("\r\n");
}
StringBuffer cookie = new StringBuffer();
if (__cookie != null) {
cookie.append("Cookie: ");
cookie.append(__cookie);
cookie.append("\r\n");
}
byte[] rb = requestBody.getBytes();
StringBuffer requestHeader = new StringBuffer();
requestHeader.append("POST ").append(url).append(" HTTP/1.1\r\n");
requestHeader.append("Host: ").append(__server.get("host")).append(':').append(__server.get("port")).append("\r\n");
requestHeader.append("User-Agent: PHPRPC Client 3.0 for Java\r\n");
requestHeader.append(auth).append(connection).append(cookie);
requestHeader.append("Accept: */*\r\n");
requestHeader.append("Accept-Encoding: gzip,deflate\r\n");
requestHeader.append("Content-Type: application/x-www-form-urlencoded; charset=").append(__charset).append("\r\n");
requestHeader.append("Content-Length: ").append(rb.length).append("\r\n");
requestHeader.append("\r\n");
__sendRequest(requestHeader.toString().getBytes(), rb);
}
private final void __sendRequest(byte[] header, byte[] body) throws IOException {
try {
BufferedOutputStream os = new BufferedOutputStream(__socket.getOutputStream());
os.write(header);
os.write(body);
os.flush();
}
catch (IOException e) {
__disconnect();
throw e;
}
}
private final void __parseHeader(HashMap responseHeader) throws PHPRPC_Error {
ArrayList xPowerdBy = (ArrayList)responseHeader.get("x-powered-by");
if (xPowerdBy == null) {
throw new PHPRPC_Error(1, "Illegal PHPRPC server.");
}
__server.put("version", "0");
for (int i = 0; i < xPowerdBy.size(); i++) {
String s = (String) xPowerdBy.get(i);
if (s.startsWith("PHPRPC Server/")) {
__server.put("version", s.substring(14));
}
}
if ((String)__server.get("version") == "0") {
throw new PHPRPC_Error(1, "Illegal PHPRPC server.");
}
if (responseHeader.containsKey("content-type")) {
ArrayList contentType = (ArrayList)responseHeader.get("content-type");
for (int i = 0; i < contentType.size(); i++) {
String s = (String) contentType.get(i);
if (s.startsWith("text/plain; charset=")) {
setCharset(s.substring(20));
}
}
}
if (responseHeader.containsKey("set-cookie")) {
String name, value;
ArrayList setCookie = (ArrayList)responseHeader.get("set-cookie");
for (int i = 0; i < setCookie.size(); i++) {
String s = (String) setCookie.get(i);
String[] cookies = s.split("[;,]\\s?");
for (int j = 0; j < cookies.length; j++) {
String[] pair = cookies[j].split("=", 2);
if (pair.length == 2) {
name = pair[0];
value = pair[1];
}
else {
name = pair[0];
value = "";
}
if (!name.equals("domain") && !name.equals("expires") &&
!name.equals("path") && !name.equals("secure")) {
__cookies.put(name, value);
}
}
}
__cookie = "";
for (Iterator keys = __cookies.keySet().iterator(); keys.hasNext();) {
name = (String) keys.next();
value = (String) __cookies.get(name);
__cookie += name + "=" + value + "; ";
}
}
if (responseHeader.containsKey("content-encoding")) {
responseHeader.put("content-encoding", ((ArrayList) responseHeader.get("content-encoding")).get(0));
}
if (responseHeader.containsKey("transfer-encoding")) {
responseHeader.put("transfer-encoding", ((ArrayList) responseHeader.get("transfer-encoding")).get(0));
}
if (responseHeader.containsKey("content-length")) {
responseHeader.put("content-length", ((ArrayList) responseHeader.get("content-length")).get(0));
}
if (responseHeader.containsKey("connection")) {
responseHeader.put("connection", ((ArrayList) responseHeader.get("connection")).get(0));
}
}
private final String __readLine(InputStream is) throws IOException {
StringBuffer sb = new StringBuffer();
int c;
while ((c = is.read()) > -1) {
if (c == 13) {
c = is.read();
if (c != 10) {
throw new IOException();
}
else {
return sb.toString();
}
}
else {
sb.append((char) c);
}
}
return sb.toString();
}
private final boolean __readCRLF(InputStream is) throws IOException {
if (is.read() != 13) {
return false;
}
if (is.read() != 10) {
return false;
}
return true;
}
private final HashMap __readResponseHeader(String requestBody) throws IOException, PHPRPC_Error {
return __readResponseHeader(requestBody, 0);
}
private final HashMap __readResponseHeader(String requestBody, int times) throws IOException, PHPRPC_Error {
__sendRequest(requestBody);
HashMap responseHeader;
InputStream is = __socket.getInputStream();
String statuscode = null, status = "";
do {
responseHeader = new HashMap();
String buf, name, value;
while (!(buf = __readLine(is)).equals("")) {
if (buf.startsWith("HTTP/")) {
statuscode = buf.substring(9, 12);
status = buf.substring(13);
}
else {
int pos = buf.indexOf(":");
if (pos > -1) {
name = buf.substring(0, pos).toLowerCase();
value = buf.substring(pos + 1).trim();
ArrayList a;
if (responseHeader.containsKey(name)) {
a = (ArrayList)responseHeader.get(name);
}
else {
a = new ArrayList();
}
a.add(value);
responseHeader.put(name, a);
}
}
}
try {
if (statuscode == null) {
throw new PHPRPC_Error(1, "Illegal HTTP server.");
}
if (!statuscode.equals("100") && !statuscode.equals("200")) {
throw new PHPRPC_Error(Integer.parseInt(statuscode), status);
}
if (statuscode.equals("200")) {
__parseHeader(responseHeader);
}
}
catch (PHPRPC_Error e) {
__disconnect();
throw e;
}
} while (statuscode.equals("100"));
return responseHeader;
}
private final byte[] __ungzip(HashMap responseHeader, byte[] responseBodyByteArray) throws IOException, PHPRPC_Error {
String contentEncoding = (String) responseHeader.get("content-encoding");
if (contentEncoding != null && contentEncoding.toLowerCase().equals("gzip")) {
ByteArrayInputStream bais = new ByteArrayInputStream(responseBodyByteArray);
GZIPInputStream gzipis = new GZIPInputStream(bais);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int n = responseBodyByteArray.length;
byte[] buf = new byte[n];
int len;
while ((len = gzipis.read(buf, 0, n)) > -1) {
baos.write(buf, 0, len);
}
responseBodyByteArray = baos.toByteArray();
}
return responseBodyByteArray;
}
private final HashMap __parseBody(byte[] responseBodyByteArray) throws IOException, PHPRPC_Error {
ByteArrayInputStream is = new ByteArrayInputStream(responseBodyByteArray);
HashMap result = new HashMap();
String buf;
while (!(buf = __readLine(is)).equals("")) {
int p = buf.indexOf("=");
if (p > -1) {
result.put(buf.substring(0, p), buf.substring(p + 2, buf.length() - 2));
}
}
return result;
}
private final HashMap __readResponseBody(HashMap responseHeader) throws IOException, PHPRPC_Error {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = __socket.getInputStream();
String te = (String) responseHeader.get("transfer-encoding");
if (te != null && te.toLowerCase().equals("chunked")) {
String s = __readLine(is);
if (s.equals("")) {
__disconnect();
return new HashMap();
}
int n = Integer.parseInt(s, 16);
while (n > 0) {
byte[] b = new byte[n];
int len;
while (n > 0 && (len = is.read(b, 0, n)) > -1) {
baos.write(b, 0, len);
n -= len;
}
if (!__readCRLF(is)) {
__disconnect();
throw new PHPRPC_Error(1, "Response is incorrect.");
}
n = Integer.parseInt(__readLine(is), 16);
}
__readLine(is);
}
else if (responseHeader.get("content-length") != null) {
int n = Integer.parseInt((String) responseHeader.get("content-length"));
byte[] b = new byte[n];
int len;
while (n > 0 && (len = is.read(b, 0, n)) > -1) {
baos.write(b, 0, len);
n -= len;
}
}
else {
byte[] b = new byte[2048];
int len;
while ((len = is.read(b, 0, 2048)) > -1) {
baos.write(b, 0, len);
}
__keepAlive = false;
__disconnect();
}
return __parseBody(__ungzip(responseHeader, baos.toByteArray()));
}
private final HashMap __post(String requestBody) throws IOException, PHPRPC_Error {
if (__socket == null || !__socket.isConnected() || __socket.isClosed() ||
__socket.isInputShutdown() || __socket.isOutputShutdown()) {
__disconnect();
__connect();
}
HashMap responseHeader, responseBody;
try {
responseHeader = __readResponseHeader(requestBody);
responseBody = __readResponseBody(responseHeader);
}
catch (IOException e) {
__disconnect();
throw e;
}
String connection = (String) responseHeader.get("connection");
if (__keepAlive && connection != null && connection.equals("close")) {
__keepAlive = false;
}
if (!__keepAlive) __disconnect();
return responseBody;
}
private final void __keyExchange() throws IOException, IllegalAccessException, NoSuchAlgorithmException, PHPRPC_Error {
if (__key != null || __encryptMode == 0) return;
HashMap result = __post("phprpc_encrypt=true&phprpc_keylen=" + __keylen);
if (result.containsKey("phprpc_keylen")) {
__keylen = Integer.parseInt((String) result.get("phprpc_keylen"));
}
else {
__keylen = 128;
}
if (result.containsKey("phprpc_encrypt")) {
HashMap encrypt = (HashMap) __phpser.unserialize(Base64.decode((String) result.get("phprpc_encrypt")), HashMap.class);
BigInteger x = (new BigInteger(__keylen - 1, new Random())).setBit(__keylen - 2);
BigInteger y = new BigInteger(Cast.toString(encrypt.get("y")));
BigInteger p = new BigInteger(Cast.toString(encrypt.get("p")));
BigInteger g = new BigInteger(Cast.toString(encrypt.get("g")));
BigInteger k = y.modPow(x, p);
byte[] key;
if (__keylen == 128) {
key = k.toByteArray();
}
else {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(k.toString().getBytes());
key = md5.digest();
}
__key = new byte[16];
for (int i = 1, n = Math.min(key.length, 16); i <= n; i++) {
__key[16 - i] = key[key.length - i];
}
__post("phprpc_encrypt=" + g.modPow(x, p).toString());
}
else {
__key = null;
__encryptMode = 0;
}
}
private final byte[] __encrypt(byte[] s, int level) {
if (__key != null && __encryptMode >= level) {
s = XXTEA.encrypt(s, __key);
}
return s;
}
private final byte[] __decrypt(byte[] s, int level) {
if (__key != null && __encryptMode >= level) {
s = XXTEA.decrypt(s, __key);
}
return s;
}
}