ultimatepp/uppdev/XmlRpc/XmlRpcServer.cpp
cxl 351994a6cc Adding uppdev....
git-svn-id: svn://ultimatepp.org/upp/trunk@328 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2008-08-15 08:36:24 +00:00

308 lines
9.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "XmlRpc.h"
#include "XmlRpcSysMethods.h"
#define EMETHOD 100
#define EBADREQUEST 400
#define ENOTFOUND 404
#define ETIMEOUT 408
#define EINTERNAL 500
#define EIMPLEMENTED 501
/**
* constructs a new Xmlrpc server. it constructs a new XmlRpc server, clears the list of Methods (including SysMethods).
*/
XmlRpcServer::XmlRpcServer() {
sysMethodsLoaded=sysMethods=false;
Clear();
//sslcontext.UseCertificate(String certificate¸ String private_key¸ bool cert_asn1 = false)
}
/**
* clears the method list. this method clears the list of methods provided by the server and reloads the SysMethods,
* if they were loaded before.
*/
void XmlRpcServer::Clear() {
sslcontext.Clear();
loadSysMethods(false);
methods.Clear();
loadSysMethods(sysMethods);
}
/**
* loads the SysMethods. this method loads or unloads the special system methods provided by the server.
* @param load if set the methods will be loaded else they will be unloaded
*/
void XmlRpcServer::loadSysMethods(bool load) {
if(load) {
if(sysMethodsLoaded)
return;
methods.Add() = new XmlRpcSysListMethods("listMethods",&methods);
methods.Add() = new XmlRpcSysMethodHelp("methodHelp",&methods);
methods.Add() = new XmlRpcSysMethodSignature("methodSignature",&methods);
sysMethodsLoaded=true;
}else {
if(!sysMethodsLoaded)
return;
for(int i=0;i<methods.GetCount();i++) {
if(methods.At(i)->GetName().Left(7) == "system.")
delete methods[i];
methods.Remove(i);
i--;
}
sysMethodsLoaded=false;
}
}
/**
* set wether SysMethods should be provided. if called with \p on = true the Sysmethods will be
* loaded and even reloaded after clearing the non system messages with Clear(). The systme messages that
* are provided at the moment are: system.listMethods, system.methodHelp, system.methodSignature.
* @param on if set SystemMethods will be provided by the server
*/
void XmlRpcServer::SysMethods(bool on) {
sysMethods=on;
loadSysMethods(on);
}
/**
* add a usermethod to the server. one can add own methods to the server by calling this method.
* The provided Method will be called if the name and one of the signatures of an incoming request
* match.
* Only methods with non empty names that don't start with "system." are allowed!
* @param m a pointer to a XmlRpcMethod derived object
* @returns reference to the current server
* @see XmlRpcMethod
*/
XmlRpcServer& XmlRpcServer::Add(XmlRpcMethod* m) {
if(m->GetName().IsEmpty())
return *this;
if(m->GetName().Left(7)=="system.")
return *this;
methods.Add(m);
return *this;
}
/**
* open a socket. open a socket, bind to it and listen.
* @param port port to listen at
* @param listen_count number of clients in count
* @param ssl use ssl encryption, default is no ssl encryption. If ssl should be used but the ssl
* context is not set XmlRpcServer trys to load the ConfigFile("cacert.pem") and ConfigFile("pkey.pem")
* if this fails the server wont start.
* @warning if ssl context is not set the peer won't be veryfied
* @returns true if succesfull, false else
*/
bool XmlRpcServer::Open(int port, int listen_count , bool ssl) {
server_port=port;
listen=listen_count;
use_ssl=ssl;
if(ssl) {
if(sslcontext.IsEmpty()) {
SSL_METHOD *meth = TLSv1_server_method();
sslcontext.Create(meth);
sslcontext.VerifyPeer(false);
String certfile(ConfigFile("cacert.pem")), keyfile(ConfigFile("pkey.pem"));
if(!FileExists(certfile) || !FileExists(keyfile)) {
String debug;
debug << "unable to find: ";
if(!FileExists(certfile))
debug << certfile << " ";
if(!FileExists(keyfile))
debug << keyfile;
debug <<"\n";
LOG(debug);
return false;
}
String cert(LoadFile(certfile));
String pkey(LoadFile(keyfile));
sslcontext.UseCertificate(cert,pkey);
}
return SSLServerSocket(socket, sslcontext, server_port, false, listen, true);
}
else
return ServerSocket(socket, server_port, false, listen, true);
}
/**
* close the socket. close the socket
*/
void XmlRpcServer::Close() {
socket.Close();
}
/**
* close and reopen the socket. close and reopen the socket by calling Close() and Open().
*/
bool XmlRpcServer::Reopen() {
Close();
return Open(server_port,listen,use_ssl);
}
/**
* wait for connections. wait for one incomming connection return after max \p msec milliseconds
* of waiting or after accepting a connection or after an error.
* @param msec time to wait for one connection before returning
*/
void XmlRpcServer::RunOnce(int msec) {
if(!socket.IsOpen())
return;
Socket s;
if(socket.Peek(msec)) {
XmlRpcConnectionInfo ci;
if(socket.Accept(s,&ci.ipadd,true,0)) {
ci.timeout=10000;
Thread().Run(THISBACK2(HandleRequest,s,RawToValue(ci)));
} else {
RLOG(NFormat("accept error: %d %s",socket.GetErrorCode(),socket.GetErrorText()));
}
}
if(socket.IsError())
RLOG(NFormat("socket error: %d %s",socket.GetErrorCode(),socket.GetErrorText()));
}
/**
* wait for connections. wait for incomming connections, accept them and pass them to
* HandleRequest() and wait again in an infinite loop.
* @see RunOnce()
*/
void XmlRpcServer::Run() {
if(!socket.IsOpen())
return;
while(true) {
RunOnce(1000);
}
}
/*
* handle incomming requests. parse and response to incomming method calls.
* @param s the socket the connection is on
* @param ipadd the ip address of the calling client
* @param value a Value containing ConnectionInfo
*/
void XmlRpcServer::HandleRequest(Socket &s, Value &value) {
XmlRpcConnectionInfo ci = ValueTo<XmlRpcConnectionInfo>(value);
String data, response;
LOG("\n\ngot a Request");
data = s.Read(ci.timeout);
if(data.IsEmpty()) {
Error(s,ETIMEOUT,"Request Timeout");
return;
}
XmlRpcParser p;
XmlNode n;
Value v;
v=p.ParseMethodCall(data);
if(v.IsError()) {
Error(s,EBADREQUEST,"Bad Request: unable to understand xml method call");
return;
}
ValueArray params(v);
if(params.GetCount()<1 || params[0].GetType() != STRING_V) {
Error(s,EBADREQUEST,"Bad Request: unable to understand xml method call");
return;
}
for(int i=0;i<methods.GetCount();i++) {
if(methods[i]->GetName()==params[0]) {
CheckSignatureAndRun(s,methods[i],params, ci.ipadd);
return;
}
}
if(response.IsEmpty())
Error(s,ENOTFOUND,"Not Found: the requested method was not found");
}
/**
* check for a signature and run the method. check wether the XmlRpcMethod object has a signature
* equal to the one of the call and if so run the method and send the response
* @param s socket the connection is on
* @param m method that should be run
* @param params the methodname and the parameter received within the method call
* @param ipadd the ip address of the calling client
*/
void XmlRpcServer::CheckSignatureAndRun(Socket& s, XmlRpcMethod* m, ValueArray params, dword ipadd) {
ValueArray va;
m->Signature(va);
LOG(NFormat("function: %s",m->GetName()));
LOG(NFormat("Signature of function: %s",Value(va).ToString()));
LOG(NFormat("Signature of call: %s",Value(params).ToString()));
if(va.IsEmpty()) {
Error(s,EINTERNAL,"internal server error");
return;
}
for(int i=0; i<va.GetCount(); i++) {
if(va[i].GetType()!=VALUEARRAY_V) {
Error(s,EINTERNAL,"internal server error");
return;
}
ValueArray vaa(va[i]);
if(vaa.GetCount()<1) {
Error(s,EINTERNAL,"internal server error ");
return;
}
if(vaa.GetCount() == (params.GetCount())) {
bool execute=true;
for(int j=1;j<vaa.GetCount() && execute;j++)
execute = execute && (vaa.Get(j).GetType() == params.Get(j).GetType());
if(execute) {
LOG("found given signature");
Value result;
params.Remove(0);
m->Execute(result,params, ipadd);
LOG(NFormat("Result: %s",result.ToString()));
if(!result.IsError()) {
SendResponse(s,result);
return;
}
Error(s,EMETHOD,NFormat("Method Error: %s",GetErrorText(result)));
return;
}
}
}
Error(s,EBADREQUEST,"Bad Request: wrong method signature");
}
/**
* create xml response. create xml response.
* @param v the value to be send
* @return returns the string containing the xml representation of v
*/
String XmlRpcServer::Response(Value& v) {
XmlNode n;
XmlNode&xml = n("methodResponse")("params")("param").Add("value");
ValueToXml(xml,v);
return AsXML(n,XML_HEADER);
}
/**
* create an error response. create an error response with \p num as error number and \p desc as
* description of the error
* @param s socket the connection is on
* @param num the error number
* @param desc the description of the error
*/
void XmlRpcServer::Error(Socket& s, int num, String desc) {
XmlNode n;
LOG(NFormat("ERROR: %s (%d)",desc,num));
Value errorcode(num);
XmlNode& code=n("methodResponse")("fault")("value")("struct").Add("member");
code.Add("name").Add().CreateText("faultCode");
code.Add("value").Add("int").Add().CreateText(errorcode.ToString());
XmlNode& text=n("methodResponse")("fault")("value")("struct").Add("member");
text.Add("name").Add().CreateText("faultString");
text.Add("value").Add("string").Add().CreateText(desc);
SendResponse(s,AsXML(n,XML_HEADER));
}
/**
* send http response. send a response with a http header and the data in \p response.
* @param s socket the connection is on
* @param response a string sent as body (should be an xml document)
*/
void XmlRpcServer::SendResponse(Socket& s, String response) {
String out;
out <<"HTTP/1.0 200 OK\r\n";
out <<"Server: Ultimate++ XmlRPC Server/any OS\r\n";
out <<"Connection: close\r\n";
out <<"Content-Length: "<<response.GetLength()<<"\r\n";
out <<"Content-Type: text/xml\r\n\r\n";
out << response;
s.Write(out);
LOG("sent");
s.Close();
LOG("closed");
}