/**********************************************************\
| |
| The implementation of PHPRPC Protocol 3.0 |
| |
| PHPRPC_Server.java |
| |
| Release 3.0.0 beta 1 |
| Copyright (c) 2005-2007 by Team-PHPRPC |
| |
| WebSite: http://www.phprpc.org/ |
| 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 2.1 as published by the Free Software Foundation |
| and appearing in the included file LICENSE. |
| |
\**********************************************************/
/* PHPRPC_Server class.
*
* Copyright (C) 2007 Ma Bingyao <andot@ujn.edu.cn>
* Version: 3.0.0 beta 1
* LastModified: Mar 20, 2007
* This library is free. You can redistribute it and/or modify it.
*
/*
* Example usage:
*
* rpc.jsp
<%@ page import="java.lang.*" %>
<%@ page import="org.phprpc.*" %>
<%
PHPRPC_Server phprpc_server = new PHPRPC_Server();
phprpc_server.add("min", Math.class);
phprpc_server.add(new String[] { "sin", "cos" }, Math.class);
phprpc_server.start(request, response);
%>
*/
package org.phprpc;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.InvocationTargetException;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.PrintStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.phprpc.util.PHPSerializer;
import org.phprpc.util.Base64;
import org.phprpc.util.XXTEA;
import org.phprpc.util.DHParams;
final class RemoteFunction {
public Object obj;
public Method[] functions;
public RemoteFunction(Object obj, Method[] functions) {
this.obj = obj;
this.functions = functions;
}
}
final public class PHPRPC_Server {
private HttpServletRequest request;
private HttpServletResponse response;
private HttpSession session;
private PHPSerializer phpser;
private PrintWriter reswriter;
private HashMap functions;
private boolean debug;
private String charset;
private boolean encode;
private boolean byref;
private boolean encrypt;
private int encryptMode;
private byte[] key;
private int keylen;
private BigInteger y;
private String output;
private String callback;
private int errno;
private String errstr;
private String cid;
synchronized private boolean add(String[] functions, Object obj, Class cls, String[] aliases) {
if (aliases == null) {
aliases = functions;
}
if (functions.length != aliases.length) {
return false;
}
Method[] methods = cls.getMethods();
for (int i = 0, n = functions.length; i < n; i++) {
ArrayList fs = new ArrayList();
for (int j = 0, m = methods.length; j < m; j++) {
int mod = methods[j].getModifiers();
if (functions[i].toLowerCase().equals(methods[j].getName().toLowerCase())
&& Modifier.isPublic(mod) && (obj == null) == (Modifier.isStatic(mod))) {
fs.add(methods[j]);
}
}
this.functions.put(aliases[i].toLowerCase(), new RemoteFunction(obj, (Method[])fs.toArray(new Method[fs.size()])));
}
return true;
}
synchronized private String[] getAllFunctions(Class cls) {
Method[] methods = cls.getMethods();
HashMap names = new HashMap();
for (int i = 0, n = methods.length; i < n; i++) {
if (Modifier.isPublic(methods[i].getModifiers())) {
String fn = methods[i].getName().toLowerCase();
names.put(fn, fn);
}
}
Object[] fo = names.keySet().toArray();
String[] fs = new String[fo.length];
System.arraycopy(fo, 0, fs, 0, fo.length);
return fs;
}
synchronized private String toHexString(int n) {
return ((n < 16) ? "0" : "") + Integer.toHexString(n);
}
synchronized private String addJsSlashes(String str) {
char[] s = str.toCharArray();
StringBuffer sb = new StringBuffer();
for (int i = 0, n = s.length; i < n; i++) {
if (s[i] <= 31 || s[i] == 34 || s[i] == 39 || s[i] == 92
|| s[i] == 127) {
sb.append("\\x");
sb.append(toHexString((int)s[i] & 0xff));
}
else {
sb.append(s[i]);
}
}
return sb.toString();
}
synchronized private String addJsSlashes(byte[] s) {
StringBuffer sb = new StringBuffer();
for (int i = 0, n = s.length; i < n; i++) {
if (s[i] <= 31 || s[i] == 34 || s[i] == 39 || s[i] == 92
|| s[i] == 127) {
sb.append("\\x");
sb.append(toHexString((int)s[i] & 0xff));
}
else {
sb.append((char)s[i]);
}
}
return sb.toString();
}
synchronized private String encodeString(String s) {
if (this.encode) {
return Base64.encode(this.phpser.getBytes(s));
}
else {
return this.addJsSlashes(s);
}
}
synchronized private String encodeString(byte[] s) {
if (this.encode) {
return Base64.encode(s);
}
else {
return this.addJsSlashes(s);
}
}
synchronized private byte[] encryptString(byte[] s, int level) {
if (this.encryptMode >= level) {
s = XXTEA.encrypt(s, this.key);
}
return s;
}
synchronized private byte[] decryptString(byte[] s, int level) {
if (this.encryptMode >= level) {
s = XXTEA.decrypt(s, this.key);
}
return s;
}
synchronized private void sendURL() throws UnsupportedEncodingException {
if (!this.request.isRequestedSessionIdValid() || this.session.isNew()) {
StringBuffer url = this.request.getRequestURL();
Enumeration e = this.request.getParameterNames();
if (e.hasMoreElements()) {
url.append('?');
do {
String query = (String)e.nextElement();
if (!query.toLowerCase().startsWith("phprpc_")) {
String[] values = request.getParameterValues(query);
for (int i = 0, n = values.length; i < n; i++) {
url.append(query).append('=').append(URLEncoder.encode(values[i], this.charset)).append('&');
}
}
} while (e.hasMoreElements());
url.setLength(url.length() - 1);
}
this.reswriter.print("phprpc_url=\"" + this.encodeString(this.response.encodeURL(url.toString())) + "\";\r\n");
}
}
synchronized private void sendCallback() {
this.reswriter.print(this.callback);
this.reswriter.flush();
}
synchronized private void sendFunctions() {
this.reswriter.print("phprpc_functions=\"" + this.encodeString(this.phpser.serialize(this.functions.keySet().toArray())) + "\";\r\n");
this.sendCallback();
}
synchronized private void sendOutput() {
if (this.encryptMode >= 3) {
this.reswriter.print("phprpc_output=\"" + this.encodeString(XXTEA.encrypt(this.phpser.getBytes(output), this.key)) + "\";\r\n");
}
else {
this.reswriter.print("phprpc_output=\"" + this.encodeString(this.output) + "\";\r\n");
}
}
synchronized private void sendError() {
this.reswriter.print("phprpc_errno=\"" + this.errno + "\";\r\n");
this.reswriter.print("phprpc_errstr=\"" + this.encodeString(this.errstr) + "\";\r\n");
this.sendOutput();
this.sendCallback();
}
synchronized private void sendHeader() throws IOException {
this.response.setContentType("text/plain; charset=" + this.charset);
this.response.addHeader("X-Powered-By", "PHPRPC Server/3.0");
this.response.setDateHeader("Expires", (new Date()).getTime());
this.response.addHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
this.reswriter = response.getWriter();
}
synchronized private byte[] call(Method function, Object obj, ArrayList arguments) throws UnsupportedEncodingException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class[] p = function.getParameterTypes();
int size = arguments.size();
if (p.length != size) {
if ((p.length == size + 1) &&
p[p.length - 1].getName() == "javax.servlet.http.HttpServletRequest") {
arguments.add(this.request);
}
else {
throw new IllegalArgumentException("argument number mismatch");
}
}
Object[] args = arguments.toArray();
if (size < arguments.size()) {
arguments.remove(size);
}
for (int i = 0, n = args.length; i < n; i++) {
if (args[i] != null) {
args[i] = this.phpser.cast(args[i], p[i]);
}
}
ByteArrayOutputStream bs = new ByteArrayOutputStream();
PrintStream defaultout = System.out;
PrintStream ps = new PrintStream(bs, true);
System.setOut(ps);
byte[] result = this.phpser.serialize(function.invoke(obj, args));
System.setOut(defaultout);
ps.close();
this.output = bs.toString();
return result;
}
synchronized private boolean getBooleanRequest(String name) {
boolean var = true;
if (this.request.getParameter(name) != null &&
this.request.getParameter(name).toLowerCase().equals("false")) {
var = false;
}
return var;
}
synchronized private void initEncode() {
this.encode = this.getBooleanRequest("phprpc_encode");
}
synchronized private void initRef() {
this.byref = this.getBooleanRequest("phprpc_ref");
}
synchronized private void initErrorHandler() {
this.errno = 0;
this.errstr = "";
this.output = "";
}
synchronized private void initCallback() {
if (this.request.getParameter("phprpc_callback") != null) {
this.callback = this.phpser.getString(Base64.decode(this.request.getParameter("phprpc_callback")));
}
else {
this.callback = "";
}
}
synchronized private void initClientID() {
this.cid = "0";
if (this.request.getParameter("phprpc_id") != null) {
this.cid = this.request.getParameter("phprpc_id");
}
this.cid = "phprpc_" + this.cid;
}
synchronized private void initKeylen() {
if (this.request.getParameter("phprpc_keylen") != null) {
this.keylen = Integer.parseInt(this.request.getParameter("phprpc_keylen"));
}
else {
HashMap sessionObject = (HashMap)this.session.getAttribute(this.cid);
if (sessionObject != null && sessionObject.get("keylen") != null){
this.keylen = ((Integer)sessionObject.get("keylen")).intValue();
}
else {
this.keylen = 128;
}
}
}
synchronized private void initEncrypt() {
this.encrypt = false;
this.encryptMode = 0;
this.y = null;
if (this.request.getParameter("phprpc_encrypt") != null) {
String encrypt = this.request.getParameter("phprpc_encrypt").toLowerCase();
if (encrypt.equals("true")) {
this.encrypt = true;
}
else if (encrypt.equals("false")) {
this.encrypt = false;
}
else if (encrypt.equals("0")) {
this.encryptMode = 0;
}
else if (encrypt.equals("1")) {
this.encryptMode = 1;
}
else if (encrypt.equals("2")) {
this.encryptMode = 2;
}
else {
this.y = new BigInteger(encrypt);
}
}
}
synchronized private void initKey() throws Exception {
HashMap sessionObject = (HashMap)this.session.getAttribute(this.cid);
if (sessionObject != null && sessionObject.get("key") != null) {
this.key = (byte[])sessionObject.get("key");
}
else if (this.encryptMode > 0) {
this.encryptMode = 0;
throw new Exception("Can't find the key for decryption.");
}
}
synchronized private ArrayList getArguments() throws UnsupportedEncodingException, IllegalAccessException {
ArrayList arguments;
if (this.request.getParameter("phprpc_args") != null) {
arguments = (ArrayList)this.phpser.unserialize(this.decryptString(Base64.decode(this.request.getParameter("phprpc_args")), 1), ArrayList.class);
}
else {
arguments = new ArrayList();
}
return arguments;
}
synchronized private void callFunction() throws Throwable {
String funcname = this.request.getParameter("phprpc_func").toLowerCase();
if (this.functions.containsKey(funcname)) {
RemoteFunction rf = (RemoteFunction)this.functions.get(funcname);
this.initKey();
ArrayList arguments = this.getArguments();
String result = null;
for (int i = 0, n = rf.functions.length; i < n; i++) {
try {
result = this.encodeString(this.encryptString(call(rf.functions[i], rf.obj, arguments), 2));
break;
}
catch (Exception e) {
if (i == n - 1) {
throw e;
}
}
}
this.reswriter.print("phprpc_result=\"" + result + "\";\r\n");
if (this.byref) {
this.reswriter.print("phprpc_args=\"" + this.encodeString(this.encryptString(this.phpser.serialize(arguments), 1)) + "\";\r\n");
}
}
else {
throw new NoSuchMethodException("Can't find this function " + this.request.getParameter("phprpc_func"));
}
this.sendError();
}
synchronized private void keyExchange() throws IOException, IllegalAccessException, NoSuchAlgorithmException {
HashMap sessionObject;
this.initKeylen();
if (this.encrypt) {
DHParams dhParams = new DHParams(this.keylen);
this.keylen = dhParams.getL();
BigInteger p = dhParams.getP();
BigInteger g = dhParams.getG();
BigInteger x = dhParams.getX();
BigInteger y = g.modPow(x, p);
sessionObject = new HashMap();
sessionObject.put("x", x);
sessionObject.put("p", p);
sessionObject.put("keylen", new Integer(this.keylen));
this.session.setAttribute(this.cid, sessionObject);
HashMap dhp = dhParams.getDHParams();
dhp.put("y", y.toString());
this.reswriter.print("phprpc_encrypt=\"" + this.encodeString(this.phpser.serialize(dhp)) + "\";\r\n");
if (this.keylen != 128) {
this.reswriter.print("phprpc_keylen=\"" + this.keylen + "\";\r\n");
}
this.sendURL();
}
else {
sessionObject = (HashMap)this.session.getAttribute(this.cid);
BigInteger x = (BigInteger)sessionObject.get("x");
BigInteger p = (BigInteger)sessionObject.get("p");
BigInteger k = this.y.modPow(x, p);
byte[] key;
if (this.keylen == 128) {
key = k.toByteArray();
}
else {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(k.toString().getBytes());
key = md5.digest();
}
this.key = new byte[16];
for (int i = 1, n = Math.min(key.length, 16); i <= n; i++) {
this.key[16 - i] = key[key.length - i];
}
sessionObject.put("key", this.key);
sessionObject.remove("x");
sessionObject.remove("p");
this.session.setAttribute(this.cid, sessionObject);
}
this.sendCallback();
}
public PHPRPC_Server() {
this.phpser = new PHPSerializer();
this.functions = new HashMap();
this.charset = "UTF-8";
this.debug = false;
}
synchronized public boolean add(Object obj) {
Class cls = obj.getClass();
return add(getAllFunctions(cls), obj, cls, null);
}
synchronized public boolean add(Class cls) {
return add(getAllFunctions(cls), null, cls, null);
}
synchronized public boolean add(String function, Object obj) {
return add(new String[] { function }, obj, obj.getClass(), null);
}
synchronized public boolean add(String function, Object obj, String alias) {
return add(new String[] { function }, obj, obj.getClass(), new String[] { alias });
}
synchronized public boolean add(String[] functions, Object obj) {
return add(functions, obj, obj.getClass(), null);
}
synchronized public boolean add(String[] functions, Object obj, String[] aliases) {
return add(functions, obj, obj.getClass(), aliases);
}
synchronized public boolean add(String function, Class cls) {
return add(new String[] { function }, null, cls, null);
}
synchronized public boolean add(String function, Class cls, String alias) {
return add(new String[] { function }, null, cls, new String[] { alias });
}
synchronized public boolean add(String[] functions, Class cls) {
return add(functions, null, cls, null);
}
synchronized public boolean add(String[] functions, Class cls, String[] aliases) {
return add(functions, null, cls, aliases);
}
synchronized public void setCharset(String charset) {
this.charset = charset;
this.phpser.setCharset(charset);
}
synchronized public void setDebugMode(boolean debug) {
this.debug = debug;
}
synchronized public void start(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
this.session = request.getSession(true);
try {
this.initErrorHandler();
this.sendHeader();
this.initClientID();
this.initEncode();
this.initCallback();
this.initRef();
this.initEncrypt();
if (this.request.getParameter("phprpc_func") != null) {
this.callFunction();
}
else if (this.encrypt != false || this.y != null) {
this.keyExchange();
}
else {
this.sendFunctions();
}
}
catch (Throwable e) {
this.errno = 1;
if (this.debug) {
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");
}
this.errstr = es.toString();
}
else {
this.errstr = e.toString();
}
this.sendError();
}
}
}