mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 06:05:58 -06:00
308 lines
9.7 KiB
C++
308 lines
9.7 KiB
C++
#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");
|
||
}
|