added resource_api and rs-nogui-webui (requires libmicrohttpd, Html files are not included)

git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@8047 b45a01b8-16f6-495d-af2f-9b41ad6348cc
This commit is contained in:
electron128 2015-03-18 18:48:43 +00:00
parent 97831d0667
commit ad1bc7f3b8
38 changed files with 5547 additions and 2 deletions

View File

@ -8,5 +8,6 @@ SUBDIRS += \
libbitdht/src/libbitdht.pro \
libretroshare/src/libretroshare.pro \
retroshare-gui/src/retroshare-gui.pro \
libresapi/src/libresapi.pro \
retroshare-nogui/src/retroshare-nogui.pro \
plugins/plugins.pro

View File

@ -0,0 +1,351 @@
#include "ApiServer.h"
#include <retroshare/rspeers.h>
#include <retroshare/rsmsgs.h>
#include <time.h>
#include <unistd.h>
#include <sstream>
#include "json.h"
#include <retroshare/rsservicecontrol.h>
#ifdef FIXME
#include "rswall.h"
#endif
#include "JsonStream.h"
#include "StateTokenServer.h" // for the state token serialisers
/*
data types in json http://json.org/
string (utf-8 unicode)
number (int and float)
object (key value pairs, key must be a string)
true
false
null
data types in lua http://www.lua.org/pil/2.html
nil
boolean
number (double)
string (8-bit)
table (key value pairs, keys can be anything except nil)
data types in QML http://qt-project.org/doc/qt-5/qtqml-typesystem-basictypes.html
bool
string
real/double
int
list
object types?
QML has many more types with special meaning like date
C++ delivers
std::string
bool
int
(double? i don't know)
enum
bitflags
raw binary data
objects
std::vector
std::list
different types of ids/hashes
-> convert to/from string with a generic operator
-> the operator signals ok/fail to the stream
*/
/*
data types to handle:
- bool
- string
- bitflags
- enums
containers:
- arrays: collection of objects or values without name, usually of the same type
- objects: objects and values with names
careful: the json lib has many asserts, so retroshare will stop if the smalles thing goes wrong
-> check type of json before usage
there are two possible implementations:
- with a virtual base class for the serialisation targets
- better documentation of the interface
- with templates
*/
/*
the general idea is:
want output in many different formats, while the retrival of the source data is always the same
get, put
ressource adress like
.org.retroshare.api.peers
generic router from adress to the ressource handler object
data formats for input and output:
- json
- lua
- QObject
- maybe just a typed c++ object
rest inspired / resource based interface
- have resources with adresses
- requests:
- put
- get
- request has parameters
- response:
- returncode:
- ok
- not modified
- error
- data = object or list of objects
want to have a typesafe interface at the top?
probably not, a system with generic return values is enough
this interface is for scripting languages which don't have types
the interface at the top should look like this:
template <class RequestDataFormatT, class ResponseDataFormatT>
processRequest(const RequestMeta& req, const RequestDataFormatT& reqData,
ResponseMeta& respMeta, ResponseDataFormatT& respData);
idea: pass all the interfaces to the retroshare core to this function,
or have this function as part of an object
the processor then applies all members of the request and response data to the data format like this:
reqData << "member1" << member1
<< "member2" << member2 ... ;
these operators have to be implemented for common things like boolean, int, std::string, std::vector, std::list ...
request data gets only deserialised
response data gets only serialised
response and request meta contains things like resource address, method and additional parameters
want generic resource caching mechanism
- on first request a request handler is created
- request handler is stored with its input data
- if a request handler for a given resource adress and parameters exists
then the request handler is asked if the result is still valid
if yes the result from the existing handler is used
- request handler gets deleted after timeout
- can have two types of resource handlers: static handlers and dynamic handlers
- static handlers don't get deleted, because they don't contain result data
- dynamic handlers contain result data, and thus get deleted after a while
it is even possible to implement a resource-changed check at the highest level
this allows to compute everything on the server side and only send chanes to the client
the different resource providers don't have to implement a resource changed check then
a top level change detector will poll them
of course this does not wokr with a deep resource tree with millions of nodes
for this we have the dynamic handlers,
they are created on demand and know how to listen for changes which affect them
*/
namespace resource_api{
// old code, only to copy and paste from
// to be removed
/*
class ChatlobbiesHandler
{
public:
ChatlobbiesHandler(RsMsgs* msgs): mMsgs(msgs) {}
template <class InputT, class OutputT>
void handleRequest(Request& req, InputT& reqData, Response& resp, OutputT& respData)
{
if(req.mMethod == "GET")
{
typename OutputT::Array result;
// subscribed lobbies
std::list<ChatLobbyInfo> slobbies;
mMsgs->getChatLobbyList(slobbies);
for(std::list<ChatLobbyInfo>::iterator lit = slobbies.begin(); lit != slobbies.end(); lit++)
{
typename OutputT::Object lobby;
ChatLobbyInfo& lobbyRecord = *lit;
lobby["name"] = lobbyRecord.lobby_name;
RsPeerId pid;
mMsgs->getVirtualPeerId(lobbyRecord.lobby_id, pid);
lobby["id"] = pid.toStdString();
lobby["subscribed"] = true;
result.push_back(lobby);
}
// unsubscirbed lobbies
std::vector<VisibleChatLobbyRecord> ulobbies;
mMsgs->getListOfNearbyChatLobbies(ulobbies);
for(std::vector<VisibleChatLobbyRecord>::iterator vit = ulobbies.begin(); vit != ulobbies.end(); vit++)
{
typename OutputT::Object lobby;
VisibleChatLobbyRecord& lobbyRecord = *vit;
lobby["name"] = lobbyRecord.lobby_name;
RsPeerId pid;
mMsgs->getVirtualPeerId(lobbyRecord.lobby_id, pid);
lobby["id"] = pid.toStdString();
lobby["subscribed"] = false;
result.push_back(lobby);
}
respData = result;
}
else if(req.mMethod == "PUT")
{
RsPeerId id = RsPeerId(req.mAdress.substr(1));
if(!id.isNull() && reqData.HasKey("msg"))
{
// for now can send only id as message
mMsgs->sendPrivateChat(id, reqData["msg"]);
}
}
}
RsMsgs* mMsgs;
};
*/
class ApiServerMainModules
{
public:
ApiServerMainModules(ResourceRouter& router, StateTokenServer* sts, const RsPlugInInterfaces &ifaces):
mPeersHandler(sts, ifaces.mNotify, ifaces.mPeers, ifaces.mMsgs),
mIdentityHandler(ifaces.mIdentity),
mServiceControlHandler(rsServiceControl), // TODO: don't use global variable here
mFileSearchHandler(sts, ifaces.mNotify, ifaces.mTurtle, ifaces.mFiles),
mTransfersHandler(sts, ifaces.mFiles)
{
// the dynamic cast is to not confuse the addResourceHandler template like this:
// addResourceHandler(derived class, parent class)
// the template would then be instantiated using derived class as parameter
// and then parent class would not match the type
router.addResourceHandler("peers",dynamic_cast<ResourceRouter*>(&mPeersHandler),
&PeersHandler::handleRequest);
router.addResourceHandler("identity", dynamic_cast<ResourceRouter*>(&mIdentityHandler),
&IdentityHandler::handleRequest);
router.addResourceHandler("servicecontrol", dynamic_cast<ResourceRouter*>(&mServiceControlHandler),
&ServiceControlHandler::handleRequest);
router.addResourceHandler("filesearch", dynamic_cast<ResourceRouter*>(&mFileSearchHandler),
&FileSearchHandler::handleRequest);
router.addResourceHandler("transfers", dynamic_cast<ResourceRouter*>(&mTransfersHandler),
&TransfersHandler::handleRequest);
}
PeersHandler mPeersHandler;
IdentityHandler mIdentityHandler;
ServiceControlHandler mServiceControlHandler;
FileSearchHandler mFileSearchHandler;
TransfersHandler mTransfersHandler;
};
#ifdef FIXME
class ApiServerWallModule
{
public:
ApiServerWallModule(ResourceRouter& router, const RsPlugInInterfaces& ifaces, RsWall::RsWall* wall):
mWallHandler(wall, ifaces.mIdentity)
{
router.addResourceHandler("wall", dynamic_cast<ResourceRouter*>(&mWallHandler),
&WallHandler::handleRequest);
}
WallHandler mWallHandler;
};
#endif
ApiServer::ApiServer():
mStateTokenServer(),
mMainModules(0),
mWallModule(0)
{
mRouter.addResourceHandler("statetokenservice", dynamic_cast<ResourceRouter*>(&mStateTokenServer),
&StateTokenServer::handleRequest);
}
ApiServer::~ApiServer()
{
if(mMainModules)
delete mMainModules;
if(mWallModule)
delete mWallModule;
}
void ApiServer::loadMainModules(const RsPlugInInterfaces &ifaces)
{
if(mMainModules == 0)
mMainModules = new ApiServerMainModules(mRouter, &mStateTokenServer, ifaces);
}
#ifdef FIXME
void ApiServer::loadWallModule(const RsPlugInInterfaces &ifaces, RsWall::RsWall* wall)
{
if(mWallModule == 0)
mWallModule = new ApiServerWallModule(mRouter, ifaces, wall);
}
#endif
std::string ApiServer::handleRequest(Request &request)
{
resource_api::JsonStream outstream;
std::stringstream debugString;
StreamBase& data = outstream.getStreamToMember("data");
resource_api::Response resp(data, debugString);
ResponseTask* task = mRouter.handleRequest(request, resp);
time_t start = time(NULL);
while(task && task->doWork(request, resp))
{
usleep(10*1000);
/*if(time(NULL) > (start+5))
{
std::cerr << "ApiServer::handleRequest() Error: task timed out" << std::endl;
resp.mDebug << "Error: task timed out." << std::endl;
resp.mReturnCode = resource_api::Response::FAIL;
break;
}*/
}
if(task)
delete task;
std::string returncode;
switch(resp.mReturnCode){
case resource_api::Response::NOT_SET:
returncode = "not_set";
break;
case resource_api::Response::OK:
returncode = "ok";
break;
case resource_api::Response::WARNING:
returncode = "warning";
break;
case resource_api::Response::FAIL:
returncode = "fail";
break;
}
// evil HACK, remove this
if(data.isRawData())
return data.getRawData();
outstream << resource_api::makeKeyValue("debug_msg", debugString.str());
outstream << resource_api::makeKeyValueReference("returncode", returncode);
if(!resp.mStateToken.isNull())
outstream << resource_api::makeKeyValueReference("statetoken", resp.mStateToken);
return outstream.getJsonString();
}
} // namespace resource_api

View File

@ -0,0 +1,86 @@
#pragma once
#include <retroshare/rsplugin.h>
#include "ApiTypes.h"
#include "PeersHandler.h"
#include "IdentityHandler.h"
#ifdef FIXME
#include "WallHandler.h"
#endif
#include "ServiceControlHandler.h"
#include "StateTokenServer.h"
#include "FileSearchHandler.h"
#include "TransfersHandler.h"
namespace resource_api{
class ApiServerMainModules;
class ApiServerWallModule;
// main entry point for all resource api calls
// call chain is like this:
// Wt -> ApiServerWt -> ApiServer -> different handlers
// later i want to replace the parts with Wt with something else
// (Wt is a too large framework, a simple http server would be enough)
// maybe use libmicrohttpd
// the other use case for this api is a qt webkit view
// this works without html, the webkitview calls directly into our c++ code
// general part of the api server
// should work with any http library or a different transport protocol
class ApiServer
{
public:
ApiServer();
~ApiServer();
// it is currently hard to separate into http and non http stuff
// mainly because the http path is used in the api
// this has to change later
// for now let the http part make the request object
// and the general apiserver part makes the response
std::string handleRequest(Request& request);
// load the main api modules
void loadMainModules(const RsPlugInInterfaces& ifaces);
// only after rswall was started!
#ifdef FIXME
void loadWallModule(const RsPlugInInterfaces& ifaces, RsWall::RsWall* wall);
#endif
// allows to add more handlers
// make sure the livetime of the handlers is longer than the api server
template <class T>
void addResourceHandler(std::string name, T* instance, ResponseTask* (T::*callback)(Request& req, Response& resp));
template <class T>
void addResourceHandler(std::string name, T* instance, void (T::*callback)(Request& req, Response& resp));
StateTokenServer* getStateTokenServer(){ return &mStateTokenServer; }
private:
StateTokenServer mStateTokenServer; // goes first, as others may depend on it
// is always loaded, because it has no dependencies
// only pointers here, to load/unload modules at runtime
ApiServerMainModules* mMainModules; // loaded when RS is started
ApiServerWallModule* mWallModule; // only loaded in rssocialnet plugin
ResourceRouter mRouter;
};
// implementations
template <class T>
void ApiServer::addResourceHandler(std::string name, T* instance, ResponseTask* (T::*callback)(Request& req, Response& resp))
{
mRouter.addResourceHandler(name, instance, callback);
}
template <class T>
void ApiServer::addResourceHandler(std::string name, T* instance, void (T::*callback)(Request& req, Response& resp))
{
mRouter.addResourceHandler(name, instance, callback);
}
}

View File

@ -0,0 +1,353 @@
#include "ApiServerMHD.h"
#include <iostream>
#include <sys/stat.h>
#include <cstdio>
#include <algorithm>
// for filestreamer
#include <retroshare/rsfiles.h>
#include "api/JsonStream.h"
namespace resource_api{
const char* API_ENTRY_PATH = "/api/v2";
const char* FILESTREAMER_ENTRY_PATH = "/fstream/";
// interface for request handler classes
class MHDHandlerBase
{
public:
virtual ~MHDHandlerBase(){}
// return MHD_NO to terminate connection
// return MHD_YES otherwise
// this function will get called by MHD until a response was queued
virtual int handleRequest( struct MHD_Connection *connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size) = 0;
};
// handles calls to the resource_api
class MHDApiHandler: public MHDHandlerBase
{
public:
MHDApiHandler(ApiServer* s): mState(BEGIN), mApiServer(s){}
virtual ~MHDApiHandler(){}
// return MHD_NO or MHD_YES
virtual int handleRequest( struct MHD_Connection *connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size)
{
// new request
if(mState == BEGIN)
{
if(strcmp(method, "POST") == 0)
{
mState = WAITING_DATA;
// first time there is no data, do nohtin and return
return MHD_YES;
}
}
if(mState == WAITING_DATA)
{
if(upload_data && *upload_data_size)
{
mRequesString += std::string(upload_data, *upload_data_size);
*upload_data_size = 0;
return MHD_YES;
}
}
if(strstr(url, API_ENTRY_PATH) != url)
{
std::cerr << "FATAL ERROR in MHDApiHandler::handleRequest(): url does not start with api entry path, which is \"" << API_ENTRY_PATH << "\"" << std::endl;
return MHD_NO;
}
std::string path2 = (url + strlen(API_ENTRY_PATH));
resource_api::JsonStream instream;
instream.setJsonString(mRequesString);
resource_api::Request req(instream);
if(strcmp(method, "GET") == 0)
{
req.mMethod = resource_api::Request::GET;
}
else if(strcmp(method, "POST") == 0)
{
req.mMethod = resource_api::Request::PUT;
}
else if(strcmp(method, "DELETE") == 0)
{
req.mMethod = resource_api::Request::DELETE_AA;
}
std::stack<std::string> stack;
std::string str;
for(std::string::reverse_iterator sit = path2.rbegin(); sit != path2.rend(); sit++)
{
if((*sit) != '/')
{
// add to front because we are traveling in reverse order
str = *sit + str;
}
else
{
if(str != "")
{
stack.push(str);
str.clear();
}
}
}
if(str != "")
{
stack.push(str);
}
req.mPath = stack;
req.mFullPath = path2;
std::string result = mApiServer->handleRequest(req);
struct MHD_Response* resp = MHD_create_response_from_data(result.size(), (void*)result.data(), 0, 1);
// EVIL HACK remove
if(result[0] != '{')
MHD_add_response_header(resp, "Content-Type", "image/png");
else
MHD_add_response_header(resp, "Content-Type", "text/plain");
MHD_queue_response(connection, MHD_HTTP_OK, resp);
MHD_destroy_response(resp);
return MHD_YES;
}
enum State {BEGIN, WAITING_DATA};
State mState;
std::string mRequesString;
ApiServer* mApiServer;
};
class MHDFilestreamerHandler: public MHDHandlerBase
{
public:
MHDFilestreamerHandler(): mSize(0){}
virtual ~MHDFilestreamerHandler(){}
RsFileHash mHash;
uint64_t mSize;
// return MHD_NO or MHD_YES
virtual int handleRequest( struct MHD_Connection *connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size)
{
if(rsFiles == 0)
{
ApiServerMHD::sendMessage(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Error: rsFiles is null. Retroshare is probably not yet started.");
return MHD_YES;
}
if(url[0] == 0 || (mHash=RsFileHash(url+strlen(FILESTREAMER_ENTRY_PATH))).isNull())
{
ApiServerMHD::sendMessage(connection, MHD_HTTP_NOT_FOUND, "Error: URL is not a valid file hash");
return MHD_YES;
}
FileInfo info;
std::list<RsFileHash> dls;
rsFiles->FileDownloads(dls);
if(!(rsFiles->alreadyHaveFile(mHash, info) || std::find(dls.begin(), dls.end(), mHash) != dls.end()))
{
ApiServerMHD::sendMessage(connection, MHD_HTTP_NOT_FOUND, "Error: file not existing on local peer and not downloading. Start the download before streaming it.");
return MHD_YES;
}
mSize = info.size;
struct MHD_Response* resp = MHD_create_response_from_callback(
mSize, 1024*1024, &contentReadercallback, this, NULL);
MHD_queue_response(connection, MHD_HTTP_OK, resp);
MHD_destroy_response(resp);
return MHD_YES;
}
static ssize_t contentReadercallback(void *cls, uint64_t pos, char *buf, size_t max)
{
MHDFilestreamerHandler* handler = (MHDFilestreamerHandler*)cls;
if(pos >= handler->mSize)
return MHD_CONTENT_READER_END_OF_STREAM;
uint32_t size_to_send = max;
if(!rsFiles->getFileData(handler->mHash, pos, size_to_send, (uint8_t*)buf))
return 0;
return size_to_send;
}
};
ApiServerMHD::ApiServerMHD(std::string root_dir, uint16_t port):
mRootDir(root_dir), mPort(port),
mDaemon(0)
{
}
ApiServerMHD::~ApiServerMHD()
{
stop();
}
bool ApiServerMHD::start()
{
if(mDaemon)
{
std::cerr << "ApiServerMHD::start() ERROR: server already started. You have to call stop() first." << std::endl;
return false;
}
mDaemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, mPort,
&static_acceptPolicyCallback, this,
&static_accessHandlerCallback, this,
MHD_OPTION_NOTIFY_COMPLETED, &static_requestCompletedCallback, this,
MHD_OPTION_END);
if(mDaemon)
{
std::cerr << "ApiServerMHD::start() SUCCESS. Started server on port " << mPort << ". mRootDir=\"" << mRootDir << "\"" << std::endl;
return true;
}
else
{
std::cerr << "ApiServerMHD::start() ERROR: starting the server failed." << std::endl;
return false;
}
}
void ApiServerMHD::stop()
{
if(mDaemon == 0)
return;
MHD_stop_daemon(mDaemon);
mDaemon = 0;
}
/*static*/ void ApiServerMHD::sendMessage(MHD_Connection *connection, unsigned int status, std::string message)
{
std::string page = "<html><body><p>"+message+"</p></body></html>";
struct MHD_Response* resp = MHD_create_response_from_data(page.size(), (void*)page.data(), 0, 1);
MHD_add_response_header(resp, "Content-Type", "text/html");
MHD_queue_response(connection, status, resp);
MHD_destroy_response(resp);
}
int ApiServerMHD::static_acceptPolicyCallback(void *cls, const sockaddr *addr, socklen_t addrlen)
{
return ((ApiServerMHD*)cls)->acceptPolicyCallback(addr, addrlen);
}
int ApiServerMHD::static_accessHandlerCallback(void* cls, struct MHD_Connection * connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size,
void **con_cls)
{
return ((ApiServerMHD*)cls)->accessHandlerCallback(connection, url, method, version,
upload_data, upload_data_size, con_cls);
}
void ApiServerMHD::static_requestCompletedCallback(void *cls, MHD_Connection* connection,
void **con_cls, MHD_RequestTerminationCode toe)
{
((ApiServerMHD*)cls)->requestCompletedCallback(connection, con_cls, toe);
}
int ApiServerMHD::acceptPolicyCallback(const sockaddr *addr, socklen_t addrlen)
{
// limit to localhost
// denies from localhost, TODO
/*
if(addr->sa_family == AF_INET && ((sockaddr_in*)addr)->sin_addr.s_addr == INADDR_LOOPBACK)
return MHD_YES;
else
return MHD_NO;
*/
return MHD_YES;
}
int ApiServerMHD::accessHandlerCallback(MHD_Connection *connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size,
void **con_cls)
{
// is this call a continuation for an existing request?
if(*con_cls)
{
return ((MHDHandlerBase*)(*con_cls))->handleRequest(connection, url, method, version, upload_data, upload_data_size);
}
// these characters are not allowe in the url, raise an error if they occour
// reason: don't want to serve files outside the current document root
const char *double_dots = "..";
if(strstr(url, double_dots))
{
const char *error = "<html><body><p>Fatal error: found double dots (\"..\") in the url. This is not allowed</p></body></html>";
struct MHD_Response* resp = MHD_create_response_from_data(strlen(error), (void*)error, 0, 1);
MHD_add_response_header(resp, "Content-Type", "text/html");
MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, resp);
MHD_destroy_response(resp);
return MHD_YES;
}
// is it a call to the resource api?
if(strstr(url, API_ENTRY_PATH) == url)
{
// create a new handler and store it in con_cls
MHDHandlerBase* handler = new MHDApiHandler(&mApiServer);
*con_cls = (void*) handler;
return handler->handleRequest(connection, url, method, version, upload_data, upload_data_size);
}
// is it a call to the filestreamer?
if(strstr(url, FILESTREAMER_ENTRY_PATH) == url)
{
// create a new handler and store it in con_cls
MHDHandlerBase* handler = new MHDFilestreamerHandler();
*con_cls = (void*) handler;
return handler->handleRequest(connection, url, method, version, upload_data, upload_data_size);
}
// else server static files
std::string filename = std::string(".") + url;
// important: binary open mode,
// else libmicrohttpd will replace crlf with lf and add garbage at the end of the file
FILE* fd = fopen(filename.c_str(), "rb");
if(fd == 0)
{
const char *error = "<html><body><p>Error: can't open the requested file.</p></body></html>";
struct MHD_Response* resp = MHD_create_response_from_data(strlen(error), (void*)error, 0, 1);
MHD_add_response_header(resp, "Content-Type", "text/html");
MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, resp);
MHD_destroy_response(resp);
return MHD_YES;
}
struct stat s;
if(fstat(fileno(fd), &s) == -1)
{
const char *error = "<html><body><p>Error: file was opened but stat failed.</p></body></html>";
struct MHD_Response* resp = MHD_create_response_from_data(strlen(error), (void*)error, 0, 1);
MHD_add_response_header(resp, "Content-Type", "text/html");
MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, resp);
MHD_destroy_response(resp);
return MHD_YES;
}
struct MHD_Response* resp = MHD_create_response_from_fd(s.st_size, fileno(fd));
MHD_queue_response(connection, MHD_HTTP_OK, resp);
MHD_destroy_response(resp);
return MHD_YES;
}
void ApiServerMHD::requestCompletedCallback(struct MHD_Connection *connection,
void **con_cls, MHD_RequestTerminationCode toe)
{
if(*con_cls)
{
delete (MHDHandlerBase*)(*con_cls);
}
}
} // namespace resource_api

View File

@ -0,0 +1,37 @@
#pragma once
#include <microhttpd.h>
#include <string>
#include "api/ApiServer.h"
namespace resource_api{
class ApiServerMHD
{
public:
ApiServerMHD(std::string root_dir, uint16_t port);
~ApiServerMHD();
bool start();
void stop();
ApiServer& getApiServer(){ return mApiServer; }
// internal helper
static void sendMessage(struct MHD_Connection* connection, unsigned int status, std::string message);
private:
// static callbacks for libmicrohttpd, they call the members below
static int static_acceptPolicyCallback(void* cls, const struct sockaddr * addr, socklen_t addrlen);
static int static_accessHandlerCallback(void* cls, struct MHD_Connection * connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls);
static void static_requestCompletedCallback(void *cls, struct MHD_Connection* connection, void **con_cls, enum MHD_RequestTerminationCode toe);
int acceptPolicyCallback(const struct sockaddr * addr, socklen_t addrlen);
int accessHandlerCallback(struct MHD_Connection * connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls);
void requestCompletedCallback(struct MHD_Connection *connection, void **con_cls, MHD_RequestTerminationCode toe);
std::string mRootDir;
uint16_t mPort;
MHD_Daemon* mDaemon;
ApiServer mApiServer;
};
} // namespace resource_api

View File

@ -0,0 +1,288 @@
#pragma once
#include <string>
#include <vector>
#include <stack>
#include <stdint.h>
#include <ostream>
namespace resource_api
{
// things to clean up:
// - temporary variables when serialising rs-ids
// - always ensure proper return values
// - add help functions
// - remove unused functions or implement them
// - add operators or functions for std::set, std::list, std::vector, std::map
// idea:
// make a second parameter like
// ValueReference(this->member, OPTIONAL); // optional signals that it is not an error if this member is missing
// make a third parameter with a type hint: time, length, kilobytes
// to make arrays
template<class T>
class ValueReference
{
public:
ValueReference(T& value): value(value){}
T& value;
};
template<class T>
ValueReference<T> makeValueReference(T& value);
template<class T>
class Value
{
public:
Value(T value): value(value){}
operator ValueReference<T>(){ return ValueReference<T>(value);}
T value;
};
template<class T>
Value<T> makeValue(T value);
// to make objects
template<class T>
class KeyValueReference
{
public:
KeyValueReference(std::string key, T& value): key(key), value(value){}
//KeyValueReference(const char* key, T& value): key(key), value(value){}
std::string key;
T& value;
};
template<class T>
KeyValueReference<T> makeKeyValueReference(std::string key, T& value);
// for serialisation
// copies the supplied value
// automatically converts itself to a KeyValueReference
template<class T>
class KeyValue
{
public:
KeyValue(std::string key, T value): key(key), value(value){}
operator KeyValueReference<T>(){ return KeyValueReference<T>(key, value);}
std::string key;
T value;
};
template<class T>
KeyValue<T> makeKeyValue(std::string key, T value);
// interface for streams
class StreamBase
{
public:
// the stream provides operators for basic data types
// everything else should be broken down by others
// the same stream can either become an object or an array stream, or a binary data object
// a binary data object is just raw binary data without any decoration
// binary data is good to pass things like images and small files
// this depends on how this stream is used
// but once the stream is used as array, then only array operations are allowed
// same with an stream used as object
// idea: can have filter streams which forward the calls to another stream
// to make debug protocols of other steam implementations
// idea: make a stream shich creates a hash from the input to detect changes in the data
// make an array
virtual StreamBase& operator<<(ValueReference<bool> value) = 0;
virtual StreamBase& operator<<(ValueReference<int> value) = 0;
virtual StreamBase& operator<<(ValueReference<double> value) = 0;
virtual StreamBase& operator<<(ValueReference<std::string> value) = 0;
// usefull if the new array member should be an array or object
// the reference should be at least valid until another method of this class gets called
virtual StreamBase& getStreamToMember() = 0;
// make an object
virtual StreamBase& operator<<(KeyValueReference<bool> keyValue) = 0;
virtual StreamBase& operator<<(KeyValueReference<int> keyValue) = 0;
virtual StreamBase& operator<<(KeyValueReference<double> keyValue) = 0;
virtual StreamBase& operator<<(KeyValueReference<std::string> keyValue) = 0;
// usefull if the new object member should be an array or object
// the reference should be at least valid until another method of this class gets called
virtual StreamBase& getStreamToMember(std::string name) = 0;
// make a binay data object (not a real object, just binary data)
// idea: can use vector.swap() to allow passing larger data items without copying
virtual StreamBase& operator<<(std::vector<uint8_t>& data) = 0;
// return true if there are more members in this object/array
// useful for array reading
virtual bool hasMore() = 0;
virtual bool serialise() = 0; // let external operators find out they should serialise or deserialise
// return true if no serialisation/deserialisation error occoured
virtual bool isOK() = 0;
virtual void setError() = 0; // let external operators set the failed bit
//virtual void addLogMsg(std::string msg) = 0;
virtual void addErrorMsg(std::string msg) = 0;
virtual std::string getLog() = 0;
virtual std::string getErrorLog() = 0;
virtual bool isRawData() = 0;
virtual std::string getRawData() = 0;// HACK, remove this
};
// todo:
// define clear rules how a response to a request should look like
// the clients should be able to know when something failed
// then it is desired to have a minimum of debug output to track the errors down
// currently no check for successful serialisation/deserialisation is performed
//
// response metadata:
// - etag, when will this result expire
// - maybe a hint how often this etag should be checked for updates
//
// outcome of a request:
// - full ok
// - partial ok
// - resource not found, invalid address or resource not available
// - not ok, internal error
// - wrong usage, parameters or POST data is wrong, like deserialisation error
// is is hard to find the cause of the error
// maybe include a comment with additional info
//
// want to include a mime type of the resulting data?
// because some data is json, othe plain text, other unknown binary stuff
// live-stream resources
// some resources like typing notifications are only valid for a short time
// resource types:
// - list, either with objects or adresses of objects.
// lists need a navigation mechanism like get objects before and get objects after
// - object
// - stream
// - binary data, for example files
// TODO: record a timestamp for each token, to allow garbage collection of very old tokens
class StateToken{
public:
StateToken(): value(0){}
StateToken(uint32_t value): value(value){}
std::string toString();
uint32_t getValue() const {return value;}
bool isNull() const {return value == 0;}
private:
uint32_t value; // 0 is reserved for invalid token
};
class Request
{
public:
Request(StreamBase& stream): mStream(stream){}
bool isGet(){ return mMethod == GET;}
bool isPut(){ return mMethod == PUT;}
bool isDelete(){ return mMethod == DELETE_AA;}
bool isExec(){ return mMethod == EXEC;}
// path is the adress to the resource
// if the path has multiple parts which get handled by different handlers,
// then each handler should pop the top element
std::stack<std::string> mPath;
std::string mFullPath;
// parameters should be used to influence the result
// for example include or exclude some information
// question: when to use parameters, and when to use the data field?
// it would be easier to have only one thing...
// UNUSED: was never implemented
//std::vector<std::pair<std::string, std::string> > mParameters;
// contains data for new resources
StreamBase& mStream;
// use the is*() methods to query the method type
//private:
enum Method { GET, PUT, DELETE_AA, EXEC};// something is wrong with DELETE, it won't compile with it
Method mMethod;
};
// new notes on responses
// later we want to send multiple requests over the same link
// and we want to be able to send the responses in a different order than the requests
// for this we need a unique token in every request which gets returned in the response
// response:
// message token
// status (ok, warning, fail)
// data (different for different resources)
// debugstring (a human readable error message in case something went wrong)
class Response
{
public:
Response(StreamBase& stream, std::ostream& debug): mReturnCode(NOT_SET), mDataStream(stream), mDebug(debug){}
// WARNING means: a valid result is available, but an error occoured
// FAIL means: the result is not valid
enum ReturnCode{ NOT_SET, OK, WARNING, FAIL};
ReturnCode mReturnCode;
StateToken mStateToken;
// the result
StreamBase& mDataStream;
// humand readable string for debug messages/logging
std::ostream& mDebug;
inline void setOk(){mReturnCode = OK;}
inline void setWarning(){ mReturnCode = WARNING;}
inline void setFail(std::string msg = ""){
mReturnCode = FAIL;
if(msg != "")
mDebug << msg << std::endl;
}
};
// if a response can not be handled immediately,
// then the handler should return a ResponseTask object
// the api server will then call the doWork() method periodically
class ResponseTask
{
public:
// return true if function should get called again
// return false when finished
virtual bool doWork(Request& req, Response& resp) = 0;
};
// implementations
template<class T>
ValueReference<T> makeValueReference(T& value)
{
return ValueReference<T>(value);
}
template<class T>
Value<T> makeValue(T value)
{
return Value<T>(value);
}
template<class T>
KeyValueReference<T> makeKeyValueReference(std::string key, T& value)
{
return KeyValueReference<T>(key, value);
}
template<class T>
KeyValue<T> makeKeyValue(std::string key, T value)
{
return KeyValue<T>(key, value);
}
} // namespace resource_api

View File

@ -0,0 +1,240 @@
#include "FileSearchHandler.h"
#include <retroshare/rsexpr.h>
#include <sstream>
#include "Operators.h"
namespace resource_api
{
FileSearchHandler::FileSearchHandler(StateTokenServer *sts, RsNotify *notify, RsTurtle *turtle, RsFiles *files):
mStateTokenServer(sts), mNotify(notify), mTurtle(turtle), mFiles(files),
mMtx("FileSearchHandler")
{
mNotify->registerNotifyClient(this);
addResourceHandler("*", this, &FileSearchHandler::handleWildcard);
addResourceHandler("create_search", this, &FileSearchHandler::handleCreateSearch);
mSearchesStateToken = mStateTokenServer->getNewToken();
}
FileSearchHandler::~FileSearchHandler()
{
mNotify->unregisterNotifyClient(this);
mStateTokenServer->discardToken(mSearchesStateToken);
}
void FileSearchHandler::notifyTurtleSearchResult(uint32_t search_id, const std::list<TurtleFileInfo>& files)
{
RsStackMutex stackMtx(mMtx); // ********** STACK LOCKED MTX **********
std::map<uint32_t, Search>::iterator mit = mSearches.find(search_id);
if(mit == mSearches.end())
return;
Search& search = mit->second;
// set to a limit of 100 for now, can have more when we have pagination
std::list<TurtleFileInfo>::const_iterator lit = files.begin();
bool changed = false;
while(search.mResults.size() < 100 && lit != files.end())
{
if(search.mHashes.find(lit->hash) == search.mHashes.end())
{
changed = true;
FileDetail det ;
det.rank = 0 ;
det.age = 0 ;
det.name = (*lit).name;
det.hash = (*lit).hash;
det.size = (*lit).size;
det.id.clear();
search.mResults.push_back(det);
search.mHashes.insert(lit->hash);
}
lit++;
}
if(changed)
{
mStateTokenServer->discardToken(search.mStateToken);
search.mStateToken = mStateTokenServer->getNewToken();
}
}
// TODO: delete searches
void FileSearchHandler::handleWildcard(Request &req, Response &resp)
{
if(!req.mPath.empty())
{
std::string str = req.mPath.top();
req.mPath.pop();
if(str.size() != 8)
{
resp.setFail("Error: id has wrong size, should be 8 characters");
return;
}
uint32_t id = 0;
// TODO fix this
for(uint8_t i = 0; i < 8; i++)
{
id += (uint32_t(str[i]-'A')) << (i*4);
}
{
RsStackMutex stackMtx(mMtx); // ********** STACK LOCKED MTX **********
std::map<uint32_t, Search>::iterator mit = mSearches.find(id);
if(mit == mSearches.end())
{
resp.setFail("Error: search id invalid");
return;
}
Search& search = mit->second;
resp.mStateToken = search.mStateToken;
resp.mDataStream.getStreamToMember();
for(std::list<FileDetail>::iterator lit = search.mResults.begin(); lit != search.mResults.end(); ++lit)
{
FileDetail& fd = *lit;
double size = fd.size;
resp.mDataStream.getStreamToMember()
<< makeKeyValueReference("id", fd.hash)
<< makeKeyValueReference("name", fd.name)
<< makeKeyValueReference("hash", fd.hash)
<< makeKeyValueReference("size", size)
<< makeKeyValueReference("rank", fd.rank);
}
}
}
else
{
// list searches
RsStackMutex stackMtx(mMtx); // ********** STACK LOCKED MTX **********
resp.mDataStream.getStreamToMember();
for(std::map<uint32_t, Search>::iterator mit = mSearches.begin(); mit != mSearches.end(); ++mit)
{
uint32_t id = mit->first;
std::string idstr;
// how many times do i have to write int to string conversation?
// a library should do this
for(uint8_t i = 0; i < 8; i++)
{
char c = ((id>>(i*4))&0xF)+'A';
idstr += c;
}
resp.mDataStream.getStreamToMember()
<< makeKeyValueReference("id", idstr)
<< makeKeyValueReference("search_string", mit->second.mSearchString);
}
resp.mStateToken = mSearchesStateToken;
resp.setOk();
}
}
static bool dirDetailToFileDetail(const DirDetails& dir, FileDetail& fd)
{
if (dir.type == DIR_TYPE_FILE)
{
fd.id = dir.id;
fd.name = dir.name;
fd.hash = dir.hash;
fd.path = dir.path;
fd.size = dir.count;
fd.age = dir.age;
fd.rank = 0;
return true;
}
else
return false;
}
// see retroshare-gui/src/gui/Searchdialog.cpp
void FileSearchHandler::handleCreateSearch(Request &req, Response &resp)
{
bool distant = false;// distant involves sending data, so have it off by default for privacy
bool local = true;
bool remote = true;
std::string search_string;
req.mStream << makeKeyValueReference("distant", distant)
<< makeKeyValueReference("local", local)
<< makeKeyValueReference("remote", remote)
<< makeKeyValueReference("search_string", search_string);
std::istringstream iss(search_string);
std::list<std::string> words;
std::string s;
while (std::getline(iss, s, ' ')) {
std::cout << s << std::endl;
words.push_back(s);
}
if(words.empty())
{
resp.setFail("Error: no search string given");
return;
}
NameExpression exprs(ContainsAllStrings,words,true) ;
LinearizedExpression lin_exp ;
exprs.linearize(lin_exp) ;
uint32_t search_id = RSRandom::random_u32();
if(distant)
{
// i have no idea what the reasons for two different search modes are
// rs-gui does it, so do we
if(words.size() == 1)
search_id = mTurtle->turtleSearch(words.front());
else
search_id = mTurtle->turtleSearch(lin_exp);
}
std::list<FileDetail> results;
if(local)
{
std::list<DirDetails> local_results;
rsFiles->SearchBoolExp(&exprs, local_results, RS_FILE_HINTS_LOCAL);
for(std::list<DirDetails>::iterator lit = local_results.begin(); lit != local_results.end(); ++lit)
{
FileDetail fd;
if(dirDetailToFileDetail(*lit, fd))
results.push_back(fd);
}
}
if(remote)
{
std::list<DirDetails> remote_results;
rsFiles->SearchBoolExp(&exprs, remote_results, RS_FILE_HINTS_REMOTE);
for(std::list<DirDetails>::iterator lit = remote_results.begin(); lit != remote_results.end(); ++lit)
{
FileDetail fd;
if(dirDetailToFileDetail(*lit, fd))
results.push_back(fd);
}
}
{
RsStackMutex stackMtx(mMtx); // ********** STACK LOCKED MTX **********
Search& search = mSearches[search_id];
search.mStateToken = mStateTokenServer->getNewToken();
search.mSearchString = search_string;
search.mResults.swap(results);
mStateTokenServer->discardToken(mSearchesStateToken);
mSearchesStateToken = mStateTokenServer->getNewToken();
}
std::string idstr;
// how many times do i have to write int to string conversation?
// a library should do this
for(uint8_t i = 0; i < 8; i++)
{
char c = ((search_id>>(i*4))&0xF)+'A';
idstr += c;
}
resp.mDataStream << makeKeyValueReference("search_id", idstr);
resp.setOk();
}
} // namespace resource_api

View File

@ -0,0 +1,43 @@
#pragma once
#include "ResourceRouter.h"
#include "StateTokenServer.h"
#include <retroshare/rsnotify.h>
#include <retroshare/rsturtle.h>
#include <retroshare/rsfiles.h>
namespace resource_api
{
class FileSearchHandler: public ResourceRouter, NotifyClient
{
public:
FileSearchHandler(StateTokenServer* sts, RsNotify* notify, RsTurtle* turtle, RsFiles* files);
virtual ~FileSearchHandler();
// from NotifyClient
virtual void notifyTurtleSearchResult(uint32_t search_id, const std::list<TurtleFileInfo>& files);
private:
void handleWildcard(Request& req, Response& resp);
void handleCreateSearch(Request& req, Response& resp);
StateTokenServer* mStateTokenServer;
RsNotify* mNotify;
RsTurtle* mTurtle;
RsFiles* mFiles;
class Search{
public:
StateToken mStateToken;
std::string mSearchString; // extra service: store the search string
std::list<FileDetail> mResults;
// a set for fast deduplication lookup
std::set<RsFileHash> mHashes;
};
RsMutex mMtx;
StateToken mSearchesStateToken;
std::map<uint32_t, Search> mSearches; // mutex protected
};
} // namespace resource_api

View File

@ -0,0 +1,48 @@
#include "GetPluginInterfaces.h"
#include <retroshare/rsplugin.h>
#include <retroshare/rsmsgs.h>
#include <retroshare/rsturtle.h>
#include <retroshare/rsdisc.h>
#include <retroshare/rsdht.h>
#include <retroshare/rsnotify.h>
#include <retroshare/rsidentity.h>
#include <retroshare/rsgxscircles.h>
#include <retroshare/rsgxsforums.h>
#include <retroshare/rsgxschannels.h>
namespace resource_api{
bool getPluginInterfaces(RsPlugInInterfaces& interfaces)
{
// when rsPlugins is null, then rs was not started
if(rsPlugins == 0)
return false;
interfaces.mFiles = rsFiles;
interfaces.mPeers = rsPeers;
interfaces.mMsgs = rsMsgs;
interfaces.mTurtle = rsTurtle;
interfaces.mDisc = rsDisc;
interfaces.mDht = rsDht;
interfaces.mNotify = rsNotify;
// gxs
interfaces.mGxsDir = "";
interfaces.mIdentity = rsIdentity;
// not exposed with global variable, can't get it
interfaces.mRsNxsNetMgr = 0;
// same as identity service, but different interface
interfaces.mGxsIdService = 0;
//
interfaces.mGxsCirlces = 0;
// not exposed with global variable
interfaces.mPgpAuxUtils = 0;
interfaces.mGxsForums = rsGxsForums;
interfaces.mGxsChannels = rsGxsChannels;
return true;
}
} // namespace resource_api

View File

@ -0,0 +1,9 @@
#pragma once
class RsPlugInInterfaces;
namespace resource_api{
bool getPluginInterfaces(RsPlugInInterfaces& interfaces);
} // namespace resource_api

View File

@ -0,0 +1,6 @@
#pragma once
#include <retroshare/rsgxsifacetypes.h>
// operators for rsgxsgrpmeta and rsgxsmsgmeta
//

View File

@ -0,0 +1,115 @@
#include "GxsResponseTask.h"
#include "Operators.h"
namespace resource_api
{
GxsResponseTask::GxsResponseTask(RsIdentity *id_service, RsTokenService *token_service):
mIdService(id_service), mTokenService(token_service),
mDone(false)
{
}
bool GxsResponseTask::doWork(Request &req, Response &resp)
{
bool ready = true;
// check if gxs requests are ready
if(mTokenService && !mWaitingTokens.empty())
{
for(std::vector<uint32_t>::iterator vit = mWaitingTokens.begin(); vit != mWaitingTokens.end(); ++vit)
{
uint8_t status = mTokenService->requestStatus(*vit);
if(status != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
{
ready = false;
}
if(status == RsTokenService::GXS_REQUEST_V2_STATUS_FAILED)
{
std::cerr << "GxsResponseTask::doWork() Error: token failed. aborting." << std::endl;
resp.setFail("GxsResponseTask::doWork() Error: token failed.");
return false; // don't continue
}
}
}
if(mIdService == 0)
{
std::cerr << "GxsResponseTask::doWork() ERROR: constucted with idservice = 0. Fix your code or report this bug." << std::endl;
resp.setFail("GxsResponseTask::doWork() ERROR: constucted with idservice = 0. Fix your code or report this bug.");
return false; // don't continue
}
// check if we have identities to fetch
bool more = true;
while(!mIdentitiesToFetch.empty() && more)
{
// there are two methods to fetch identity data:
// - request gxs group, get token, get group
// - the direct way where we may have to wait, but identities will cache the result
// if we need to get many identuties, then we may flush the cache
// but if we reaquest the groups, no caching is done on the rs side (OS will cache the identities file)
// it has to be measured what is better
RsGxsId id = mIdentitiesToFetch.back();
RsIdentityDetails details;
if(mIdService->getIdDetails(id, details))
{
mIdentitiesToFetch.pop_back();
mIdentityDetails.push_back(details);
}
else
{
more = false; // pause when an id failed, to give the service time tim fetch the data
ready = false;
}
}
if(!ready)
return true; // want to continue later
mWaitingTokens.clear();
mIdentitiesToFetch.clear();
gxsDoWork(req, resp);
if(mDone) return false;
else return true;
}
void GxsResponseTask::addWaitingToken(uint32_t token)
{
if(mTokenService)
mWaitingTokens.push_back(token);
else
std::cerr << "GxsResponseTask::addWaitingToken() ERROR: constructed with tokenservice=0. Unable to handle token processing. Fix your code or report this bug." << std::endl;
}
void GxsResponseTask::done()
{
mDone = true;
}
void GxsResponseTask::requestGxsId(RsGxsId id)
{
mIdentitiesToFetch.push_back(id);
}
void GxsResponseTask::streamGxsId(RsGxsId id, StreamBase &stream)
{
// will see if this works or if we have to use an index
for(std::vector<RsIdentityDetails>::iterator vit = mIdentityDetails.begin();
vit != mIdentityDetails.end(); ++vit)
{
if(vit->mId == id)
{
stream << makeKeyValueReference("id", id)
<< makeKeyValueReference("gxs_id", id)
<< makeKeyValueReference("is_own", vit->mIsOwnId)
<< makeKeyValueReference("name", vit->mNickname)
<< makeKeyValueReference("pgp_linked", vit->mPgpLinked)
<< makeKeyValueReference("pgp_known", vit->mPgpKnown);
return;
}
}
}
} // namespace resource_api

View File

@ -0,0 +1,50 @@
#pragma once
#include "ApiTypes.h"
#include <retroshare/rsidentity.h>
namespace resource_api
{
// parent class for all responses that use the gxs backend
// this class implements the polling for gxs-tokens
// child classes pass gxs tokens to this class
// question: should this class be able to handle tokens from different services?
// then we would have to store a pointer to the token service for ever token
class GxsResponseTask: public ResponseTask
{
public:
// token service is allowed to be null if no token functions are wanted
GxsResponseTask(RsIdentity* id_service, RsTokenService* token_service);
virtual bool doWork(Request &req, Response& resp);
protected:
// this method gets called when all the pending tokens have either status ok or fail
// (= when the requests for these tokens are processed)
// how will the child class find out if a request failed?
// idea: don't call gxsDoWork() when a request failed, instead set the api response to fail
// con: then the child class has no way to tell the outside world which request failed
// pro: child class gets simpler, because no special error handling is required
// implement this in a child class
virtual void gxsDoWork(Request& req, Response& resp) = 0;
// call this to wait for tokens before the next call to gxsDoWork()
void addWaitingToken(uint32_t token);
// call this to end the task
void done();
// request name for gxs id
void requestGxsId(RsGxsId id);
// call stream function in the next cycle, then the names are available
void streamGxsId(RsGxsId id, StreamBase& stream);
private:
RsIdentity* mIdService;
RsTokenService* mTokenService;
std::vector<uint32_t> mWaitingTokens;
bool mDone;
std::vector<RsGxsId> mIdentitiesToFetch;
std::vector<RsIdentityDetails> mIdentityDetails;
};
} // namespace resource_api

View File

@ -0,0 +1,133 @@
#include "IdentityHandler.h"
#include <retroshare/rsidentity.h>
#include "Operators.h"
#include "ApiTypes.h"
#include "GxsResponseTask.h"
#ifndef WINDOWS_SYS
#include "unistd.h"
#endif
namespace resource_api
{
class SendIdentitiesListTask: public GxsResponseTask
{
public:
SendIdentitiesListTask(RsIdentity* idservice, std::list<RsGxsId> ids):
GxsResponseTask(idservice, 0)
{
for(std::list<RsGxsId>::iterator vit = ids.begin(); vit != ids.end(); ++vit)
{
requestGxsId(*vit);
mIds.push_back(*vit);// convert fro list to vector
}
}
private:
std::vector<RsGxsId> mIds;
protected:
virtual void gxsDoWork(Request &req, Response &resp)
{
resp.mDataStream.getStreamToMember();
for(std::vector<RsGxsId>::iterator vit = mIds.begin(); vit != mIds.end(); ++vit)
{
streamGxsId(*vit, resp.mDataStream.getStreamToMember());
}
resp.setOk();
done();
}
};
IdentityHandler::IdentityHandler(RsIdentity *identity):
mRsIdentity(identity)
{
addResourceHandler("*", this, &IdentityHandler::handleWildcard);
addResourceHandler("own", this, &IdentityHandler::handleOwn);
}
void IdentityHandler::handleWildcard(Request &req, Response &resp)
{
bool ok = true;
if(req.isPut())
{
RsIdentityParameters params;
req.mStream << makeKeyValueReference("name", params.nickname);
if(req.mStream.isOK())
{
uint32_t token;
mRsIdentity->createIdentity(token, params);
// not sure if should acknowledge the token
// for now go the easier way
}
else
{
ok = false;
}
}
else
{
RsTokReqOptions opts;
opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA;
uint32_t token;
mRsIdentity->getTokenService()->requestGroupInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts);
time_t start = time(NULL);
while((mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
&&(mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED)
&&((time(NULL) < (start+10)))
)
{
#ifdef WINDOWS_SYS
Sleep(500);
#else
usleep(500*1000) ;
#endif
}
if(mRsIdentity->getTokenService()->requestStatus(token) == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
{
std::vector<RsGxsIdGroup> grps;
ok &= mRsIdentity->getGroupData(token, grps);
for(std::vector<RsGxsIdGroup>::iterator vit = grps.begin(); vit != grps.end(); vit++)
{
RsGxsIdGroup& grp = *vit;
KeyValueReference<RsGxsGroupId> id("id", grp.mMeta.mGroupId);
KeyValueReference<RsPgpId> pgp_id("pgp_id",grp.mPgpId );
// not very happy about this, i think the flags should stay hidden in rsidentities
bool own = (grp.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN);
bool pgp_linked = (grp.mMeta.mGroupFlags & RSGXSID_GROUPFLAG_REALID);
resp.mDataStream.getStreamToMember()
<< id
<< pgp_id
<< makeKeyValueReference("name", grp.mMeta.mGroupName)
<< makeKeyValueReference("own", own)
<< makeKeyValueReference("pgp_linked", pgp_linked);
}
}
else
{
ok = false;
}
}
if(ok)
{
resp.setOk();
}
else
{
resp.setFail();
}
}
ResponseTask* IdentityHandler::handleOwn(Request &req, Response &resp)
{
std::list<RsGxsId> ids;
mRsIdentity->getOwnIds(ids);
return new SendIdentitiesListTask(mRsIdentity, ids);
}
} // namespace resource_api

View File

@ -0,0 +1,19 @@
#pragma once
#include "ResourceRouter.h"
class RsIdentity;
namespace resource_api
{
class IdentityHandler: public ResourceRouter
{
public:
IdentityHandler(RsIdentity* identity);
private:
RsIdentity* mRsIdentity;
void handleWildcard(Request& req, Response& resp);
ResponseTask *handleOwn(Request& req, Response& resp);
};
} // namespace resource_api

View File

@ -0,0 +1,516 @@
#include "JsonStream.h"
#include <iostream>
namespace resource_api
{
JsonStream::JsonStream():
mSerialise(true), mDataType(TYPE_UNDEFINED), mArrayNextRead(0), mIsOk(true), mChild(NULL)
{
}
JsonStream::~JsonStream()
{
deleteCurrentChild();
}
void JsonStream::setJsonString(std::string jsonStr)
{
mRawString = jsonStr;
// have to delay the deserialisation, because this stream can also be a raw data stream without json
// can find this out when others atucally call the operators
mSerialise = false;
}
std::string JsonStream::getJsonString()
{
deleteCurrentChild();
if(mIsOk)
{
switch(mDataType)
{
case TYPE_UNDEFINED:
return "";
case TYPE_ARRAY:
return json::Serialize(mArray);
case TYPE_OBJECT:
return json::Serialize(mObject);
case TYPE_RAW:
return mRawString;
default:
return "";
}
}
std::cerr << "JsonStream::getJsonString() Warning: stream not ok, will return empty string." << std::endl;
return "";
}
//----------Stream Interface ---------------
//----------Array---------------
StreamBase& JsonStream::operator<<(ValueReference<bool> value)
{
if(serialise())
{
setType(TYPE_ARRAY);
mArray.push_back(value.value);
}
else
{
if(checkDeserialisation() && arrayBoundsOk())
{
valueToBool(mArray[mArrayNextRead], value.value);
mArrayNextRead++;
}
}
return *this;
}
StreamBase& JsonStream::operator<<(ValueReference<int> value)
{
if(serialise())
{
setType(TYPE_ARRAY);
mArray.push_back(value.value);
}
else
{
if(checkDeserialisation() && arrayBoundsOk())
{
valueToInt(mArray[mArrayNextRead], value.value);
mArrayNextRead++;
}
}
return *this;
}
StreamBase& JsonStream::operator<<(ValueReference<double> value)
{
if(serialise())
{
setType(TYPE_ARRAY);
mArray.push_back(value.value);
}
else
{
if(checkDeserialisation() && arrayBoundsOk())
{
valueToDouble(mArray[mArrayNextRead], value.value);
mArrayNextRead++;
}
}
return *this;
}
StreamBase& JsonStream::operator<<(ValueReference<std::string> value)
{
if(serialise())
{
setType(TYPE_ARRAY);
mArray.push_back(value.value);
}
else
{
if(checkDeserialisation() && arrayBoundsOk())
{
valueToString(mArray[mArrayNextRead], value.value);
mArrayNextRead++;
}
}
return *this;
}
StreamBase& JsonStream::getStreamToMember()
{
setType(TYPE_ARRAY);
deleteCurrentChild();
mChild = new JsonStream();
if(!serialise())
{
if(checkDeserialisation() && arrayBoundsOk())
{
mChild->mValue = mArray[mArrayNextRead];
mArrayNextRead++;
}
}
return *mChild;
}
//----------Object---------------
StreamBase& JsonStream::operator<<(KeyValueReference<bool> keyValue)
{
if(serialise())
{
setType(TYPE_OBJECT);
mObject[keyValue.key] = keyValue.value;
}
else
{
if(checkDeserialisation() && checkObjectMember(keyValue.key))
{
valueToBool(mObject[keyValue.key], keyValue.value);
}
}
return *this;
}
StreamBase& JsonStream::operator<<(KeyValueReference<int> keyValue)
{
if(serialise())
{
setType(TYPE_OBJECT);
mObject[keyValue.key] = keyValue.value;
}
else
{
if(checkDeserialisation() && checkObjectMember(keyValue.key))
{
valueToInt(mObject[keyValue.key], keyValue.value);
}
}
return *this;
}
StreamBase& JsonStream::operator<<(KeyValueReference<double> keyValue)
{
if(serialise())
{
setType(TYPE_OBJECT);
mObject[keyValue.key] = keyValue.value;
}
else
{
if(checkDeserialisation() && checkObjectMember(keyValue.key))
{
valueToDouble(mObject[keyValue.key], keyValue.value);
}
}
return *this;
}
StreamBase& JsonStream::operator<<(KeyValueReference<std::string> keyValue)
{
if(serialise())
{
setType(TYPE_OBJECT);
mObject[keyValue.key] = keyValue.value;
}
else
{
if(checkDeserialisation() && checkObjectMember(keyValue.key))
{
valueToString(mObject[keyValue.key], keyValue.value);
}
}
return *this;
}
// usefull if the new object member should be an array or object
// the reference should be at least valid until another method of this class gets called
StreamBase& JsonStream::getStreamToMember(std::string name)
{
setType(TYPE_OBJECT);
deleteCurrentChild();
mChildKey = name;
mChild = new JsonStream();
if(!serialise())
{
if(checkDeserialisation() && checkObjectMember(name))
{
mChild->mValue = mObject[name];
}
}
return *mChild;
}
// make a binay data object (not a real object, just binary data)
StreamBase& JsonStream::operator<<(std::vector<uint8_t>& data)
{
if(serialise())
{
if((mDataType == TYPE_UNDEFINED)||(mDataType == TYPE_RAW))
{
mDataType = TYPE_RAW;
mRawString = std::string(data.begin(), data.end());
}
else
{
mErrorLog += "Error: trying to set raw data while the type of this object is already another type\n";
mIsOk = false;
}
}
else
{
if((mDataType == TYPE_UNDEFINED)||(mDataType == TYPE_RAW))
{
mDataType = TYPE_RAW;
data = std::vector<uint8_t>(mRawString.begin(), mRawString.end());
}
else
{
mErrorLog += "Error: trying to read raw data while the type of this object is already another type\n";
mIsOk = false;
}
}
return *this;
}
// return true if there are more members in this object/array
// useful for array reading
bool JsonStream::hasMore()
{
return arrayBoundsOk();
}
bool JsonStream::serialise()
{
return mSerialise;
}
bool JsonStream::isOK()
{
return mIsOk;
}
void JsonStream::setError()
{
mIsOk = false;
}
/*
void JsonStream::addLogMsg(std::string msg)
{}
*/
void JsonStream::addErrorMsg(std::string msg)
{
mErrorLog += msg;
}
std::string JsonStream::getLog()
{
return "not implemented yet";
}
std::string JsonStream::getErrorLog()
{
return mErrorLog;
}
bool JsonStream::isRawData()
{
return mDataType == TYPE_RAW;
}
std::string JsonStream::getRawData()
{
return mRawString;
}
void JsonStream::setType(DataType type)
{
if((mDataType == TYPE_UNDEFINED)||(mDataType == type))
{
mDataType = type;
}
else
{
mIsOk = false;
mErrorLog += "JsonStream::setType() Error: type alread set to another type\n";
}
}
bool JsonStream::checkObjectMember(std::string key)
{
if(mDataType == TYPE_OBJECT)
{
if(mObject.HasKey(key))
{
return true;
}
else
{
mErrorLog += "JsonStream::checkObjectMember() Warning: missing key \""+key+"\"\n";
return false;
}
}
else
{
mIsOk = false;
mErrorLog += "JsonStream::checkObjectMember() Error: type is not TYPE_OBJECT\n";
return false;
}
}
bool JsonStream::arrayBoundsOk()
{
if(checkDeserialisation())
{
if(mDataType == TYPE_ARRAY)
{
if(mArrayNextRead < mArray.size())
{
return true;
}
else
{
return false;
}
}
else
{
mIsOk = false;
mErrorLog += "JsonStream::arrayBoundsOk() Error: type is not TYPE_ARRAY\n";
return false;
}
}
return false;
}
bool JsonStream::checkDeserialisation()
{
if(mDataType != TYPE_RAW)
{
if(mDataType == TYPE_UNDEFINED)
{
if((mValue.GetType() == json::NULLVal) && mRawString != "")
{
mValue = json::Deserialize(mRawString);
}
if(mValue.GetType() == json::ObjectVal)
{
mDataType = TYPE_OBJECT;
mObject = mValue;
return true;
}
else if(mValue.GetType() == json::ArrayVal)
{
mDataType = TYPE_ARRAY;
mArray = mValue;
return true;
}
else
{
mIsOk = false;
mErrorLog += "JsonStream::checkDeserialisation() Error: deserialisation did not end with an object or array\n";
return false;
}
}
else
{
// already deserialised
return true;
}
}
else
{
mIsOk = false;
mErrorLog += "JsonStream::checkDeserialisation() Error: type is TYPE_RAW\n";
return false;
}
}
void JsonStream::valueToBool(json::Value &value, bool &boolean)
{
if(value.GetType() == json::BoolVal)
{
boolean = value;
}
else
{
mIsOk = false;
mErrorLog += "JsonStream::valueToBool() Error: wrong type\n";
}
}
void JsonStream::valueToInt(json::Value &value, int &integer)
{
if(value.GetType() == json::IntVal)
{
integer = value;
}
else
{
mIsOk = false;
mErrorLog += "JsonStream::valueToInt() Error: wrong type\n";
}
}
void JsonStream::valueToDouble(json::Value &value, double &doubleVal)
{
if(value.IsNumeric())
{
doubleVal = value;
}
else
{
mIsOk = false;
mErrorLog += "JsonStream::valueToDouble() Error: wrong type\n";
}
}
void JsonStream::valueToString(json::Value &value, std::string& str)
{
if(value.GetType() == json::StringVal)
{
str = value.ToString();
}
else
{
mIsOk = false;
mErrorLog += "JsonStream::valueToString() Error: wrong type\n";
}
}
void JsonStream::deleteCurrentChild()
{
if(mChild)
{
if(serialise())
{
if(mDataType == TYPE_ARRAY)
{
// don't add empty value
if(mChild->getJsonValue().GetType() != json::NULLVal)
mArray.push_back(mChild->getJsonValue());
}
else if(mDataType == TYPE_OBJECT)
{
mObject[mChildKey] = mChild->getJsonValue();
}
else
{
mErrorLog += "JsonStream::deleteCurrentChild() Error: cannot add child because own type is wrong\n";
}
}
else
{
// don't have to do anything for deserialisation
}
delete mChild;
mChild = NULL;
}
}
json::Value JsonStream::getJsonValue()
{
// remove the child and add it to own data
deleteCurrentChild();
switch(mDataType)
{
case TYPE_UNDEFINED:
return json::Value();
case TYPE_ARRAY:
return mArray;
case TYPE_OBJECT:
return mObject;
case TYPE_RAW:
return mRawString;
default:
return json::Value();
}
}
} // namespace resource_api

View File

@ -0,0 +1,101 @@
#pragma once
#include "ApiTypes.h"
#include "json.h"
namespace resource_api
{
class JsonStream: public StreamBase
{
public:
JsonStream();
virtual ~JsonStream();
void setJsonString(std::string jsonStr);
std::string getJsonString();
//----------Stream Interface ---------------
// make an array
virtual StreamBase& operator<<(ValueReference<bool> value);
virtual StreamBase& operator<<(ValueReference<int> value);
virtual StreamBase& operator<<(ValueReference<double> value);
virtual StreamBase& operator<<(ValueReference<std::string> value);
// usefull if the new array member should be an array or object
// the reference should be at least valid until another method of this class gets called
virtual StreamBase& getStreamToMember();
// make an object
virtual StreamBase& operator<<(KeyValueReference<bool> keyValue);
virtual StreamBase& operator<<(KeyValueReference<int> keyValue);
virtual StreamBase& operator<<(KeyValueReference<double> keyValue);
virtual StreamBase& operator<<(KeyValueReference<std::string> keyValue);
// usefull if the new object member should be an array or object
// the reference should be at least valid until another method of this class gets called
virtual StreamBase& getStreamToMember(std::string name);
// make a binay data object (not a real object, just binary data)
// idea: can use vector.swap() to allow passing larger data items without copying
virtual StreamBase& operator<<(std::vector<uint8_t>& data);
// return true if there are more members in this object/array
// useful for array reading
virtual bool hasMore();
virtual bool serialise(); // let external operators find out they should serialise or deserialise
// return true if no serialisation/deserialisation error occoured
virtual bool isOK();
virtual void setError(); // let external operators set the failed bit
//virtual void addLogMsg(std::string msg); // mayb remove? (put log messages to error log einstead)
virtual void addErrorMsg(std::string msg);
virtual std::string getLog();
virtual std::string getErrorLog();
virtual bool isRawData();
virtual std::string getRawData();
private:
bool mSerialise;
enum DataType{ TYPE_UNDEFINED, TYPE_ARRAY, TYPE_OBJECT, TYPE_RAW };
// check if the current type is undefined
// if not check if the new type matches the old type
// if not set the error bit
void setType(DataType type);
DataType mDataType;
json::Value mValue;
json::Object mObject;
// check if we are and object
// check if this key exists
bool checkObjectMember(std::string key);
json::Array mArray;
size_t mArrayNextRead;
// check if we are an array
// check if next read is valid
// if not set error bit
bool arrayBoundsOk();
std::string mRawString;
bool mIsOk;
std::string mErrorLog;
// try serialisation and set error bit on error
bool checkDeserialisation();
// check if value has correct type
// if yes return the extracted value
// if not then set the error bit
void valueToBool(json::Value& value, bool& boolean);
void valueToInt(json::Value& value, int& integer);
void valueToDouble(json::Value& value, double& doubleVal);
void valueToString(json::Value& value, std::string& str);
void deleteCurrentChild();
json::Value getJsonValue();
JsonStream* mChild;
std::string mChildKey;
};
} // namespace resource_api

View File

@ -0,0 +1,133 @@
#include "Operators.h"
namespace resource_api
{
StreamBase& operator <<(StreamBase& left, KeyValueReference<uint32_t> ref)
{
if(left.serialise())
{
uint32_t num = ref.value;
uint8_t digit;
std::string str;
while(num >= 10)
{
digit = num % 10;
num = num / 10;
str += (char)(digit + '0');
}
str += (char)(num + '0');
left << makeKeyValueReference(ref.key, str);
}
else
{
std::string str;
left << makeKeyValueReference(ref.key, str);
uint32_t num = 0;
for(std::string::iterator sit = str.begin(); sit != str.end(); sit++)
{
uint32_t numbefore = num;
num = num * 10;
if(num < numbefore)
{
left.addErrorMsg("operator for uint32_t to std::string: oveflow");
left.setError();
}
else if((*sit)<'0' || (*sit)>'9')
{
left.addErrorMsg("operator for uint32_t to std::string: invalid characters");
left.setError();
}
else
{
// everything ok, can add value
num += (*sit) - '0';
}
}
}
return left;
}
/*
template<uint32_t ID_SIZE, bool ID_UPPER, uint32_t ID_ID>
StreamBase& operator <<(StreamBase& left, t_RsGenericIdType<ID_SIZE, ID_UPPER, ID_ID>& id)
{
if(left.serialise())
{
left << id.toStdString();
}
else
{
std::string str;
left << str;
}
return left;
}
*/
template<class T_ID>
StreamBase& operator <<(StreamBase& left, ValueReference<T_ID>& ref)
{
if(left.serialise())
{
left << makeValueReference(ref.value.toStdString());
}
else
{
std::string str;
left << makeValueReference(str);
T_ID id(str);
if(id.isNull)
{
left.setError();
left.addErrorMsg("operator for retroshare id value: id is null\n");
}
ref.value = id;
}
return left;
}
// idea: have external operators which do the translation form different containers to basic operations
// better idea: take input iterators as arguments, will then work with everything which has an iterator
// but what about deserilisation?
template<template <class> class ContainerT, class ValueT>
StreamBase& operator<<(StreamBase& left, ContainerT<ValueT>& right)
{
if(left.serialise())
{
typename ContainerT<ValueT>::iterator vit;
for(vit = right.begin(); vit != right.end(); vit++)
{
left << ValueReference<ValueT>(*vit);
}
}
else
{
while(left.hasMore())
{
ValueReference<ValueT> ref;
left << ref;
right.push_back(ref.value);
}
}
return left;
}
// maybe like this:
template<class ItertatorT>
class Array
{
public:
Array(ItertatorT begin, ItertatorT end): begin(begin), end(end) {}
ItertatorT begin;
ItertatorT end;
};
} // namespace resource_api

View File

@ -0,0 +1,93 @@
#pragma once
#include <retroshare/rstypes.h>
#include "ApiTypes.h"
namespace resource_api
{
// note: pass the KeyValueReference and ValueReference objects by value to enable such things:
// stream << somefunc(); // can't get a reference to the return value of somefunc
// uint32_t to std::string with decimal numbers
StreamBase& operator <<(StreamBase& left, KeyValueReference<uint32_t> ref);
// convert retroshare ids to strings and back
//template<uint32_t ID_SIZE, bool ID_UPPER, uint32_t ID_ID>
//StreamBase& operator <<(StreamBase& left, t_RsGenericIdType<ID_SIZE, ID_UPPER, ID_ID>& id);
// operators for retroshare ids
/*
template<class T_ID>
StreamBase& operator <<(StreamBase& left, ValueReference<T_ID>& ref);
*/
template<class T_ID>
StreamBase& operator <<(StreamBase& left, KeyValueReference<T_ID> ref);
template<class T_ID>
StreamBase& operator <<(StreamBase& left, ValueReference<T_ID> ref);
//template<uint32_t ID_SIZE, bool ID_UPPER, uint32_t ID_ID>
//StreamBase& operator <<(StreamBase& left, KeyValueReference<t_RsGenericIdType<ID_SIZE, ID_UPPER, ID_ID> >& ref);
// implementations
// idea: each rs generic id type has a number
// put this number in front of the id data to make the ids type safe, even across languages
template<class T_ID>
StreamBase& operator <<(StreamBase& left, KeyValueReference<T_ID> ref)
//template<uint32_t ID_SIZE, bool ID_UPPER, uint32_t ID_ID>
//StreamBase& operator <<(StreamBase& left, KeyValueReference<t_RsGenericIdType<ID_SIZE, ID_UPPER, ID_ID> >& ref)
{
if(left.serialise())
{
std::string idStr = ref.value.toStdString();
left << makeKeyValueReference(ref.key, idStr);
}
else
{
std::string str;
left << makeKeyValueReference(ref.key, str);
//t_RsGenericIdType<ID_SIZE, ID_UPPER, ID_ID> id(str);
T_ID id(str);
if(id.isNull())
{
left.setError();
left.addErrorMsg("operator for retroshare id keyValue: id is null\n");
}
ref.value = id;
}
return left;
}
template<class T_ID>
StreamBase& operator <<(StreamBase& left, ValueReference<T_ID> ref)
{
if(left.serialise())
{
std::string idStr = ref.value.toStdString();
left << makeValueReference(idStr);
}
else
{
std::string str;
left << makeValueReference(str);
T_ID id(str);
if(id.isNull())
{
left.setError();
left.addErrorMsg("operator for retroshare id Value: id is null\n");
}
ref.value = id;
}
return left;
}
} // namespace resource_api

View File

@ -0,0 +1,79 @@
#pragma once
#include "ApiTypes.h"
namespace resource_api
{
// C must be a type with STL like iterator, a begin() and an end() method
// additionally a function id() which gives a unique value for every container element
// the type of the id should be string
// the type returned by dereferencing the iterator should have a stream operator for StreamBase
// the stream operator must not add an element "id", this is done by the pagination handler
template<class C>
void handlePaginationRequest(Request& req, Response& resp, const C& data)
{
if(!req.isGet()){
resp.mDebug << "unsupported method. only GET is allowed." << std::endl;
resp.setFail();
return;
}
if(data.begin() == data.end()){
// set result type to list
resp.mDataStream.getStreamToMember();
resp.mDebug << "note: list is empty" << std::endl;
return;
}
std::string first;
std::string last;
req.mStream << makeKeyValueReference("first", first) << makeKeyValueReference("last", last);
C::iterator it_first = data.begin();
if(first != "begin")
{
while(it_first != data.end() && id(*it_first) != first)
it_first++;
if(it_first == data.end())
{
resp.setFail("Error: first id did not match any element");
return;
}
}
C::iterator it_last = data.begin();
if(last == "end")
{
it_last = data.end();
}
else
{
while(it_last != data.end() && id(*it_last) != last)
it_last++;
if(it_last == data.end())
{
resp.setFail("Error: last id did not match any element");
return;
}
++it_last; // increment to get iterator to element after the last wanted element
}
int count = 0;
for(C::iterator it = it_first; it != it_last; ++it)
{
StreamBase& stream = resp.mDataStream.getStreamToMember();
stream << *it;
stream << makeKeyValue("id", id(*it));
// todo: also handle the case when the last element is specified and the first element is begin
// then want to return the elements near the specified element
count++;
if(count > 20){
resp.mDebug << "limited the number of returned items to 20" << std::endl;
break;
}
}
resp.setOk();
}
} // namespace resource_api

View File

@ -0,0 +1,223 @@
#include "PeersHandler.h"
#include <retroshare/rspeers.h>
#include <retroshare/rsmsgs.h>
#include <util/radix64.h>
#include <algorithm>
#include "Operators.h"
#include "ApiTypes.h"
namespace resource_api
{
// todo: groups, add friend, remove friend, permissions
void peerDetailsToStream(StreamBase& stream, RsPeerDetails& details)
{
stream
<< makeKeyValueReference("peer_id", details.id)
<< makeKeyValueReference("name", details.name)
<< makeKeyValueReference("location", details.location)
<< makeKeyValueReference("pgp_id", details.gpg_id)
;
}
bool peerInfoToStream(StreamBase& stream, RsPeerDetails& details, RsPeers* peers, std::list<RsGroupInfo>& grpInfo)
{
bool ok = true;
peerDetailsToStream(stream, details);
stream << makeKeyValue("is_online", peers->isOnline(details.id));
std::string avatar_address = "/"+details.id.toStdString()+"/avatar_image";
stream << makeKeyValue("avatar_address", avatar_address);
StreamBase& grpStream = stream.getStreamToMember("groups");
for(std::list<RsGroupInfo>::iterator lit = grpInfo.begin(); lit != grpInfo.end(); lit++)
{
RsGroupInfo& grp = *lit;
if(std::find(grp.peerIds.begin(), grp.peerIds.end(), details.gpg_id) != grp.peerIds.end())
{
grpStream.getStreamToMember()
<< makeKeyValueReference("group_name", grp.name)
<< makeKeyValueReference("group_id", grp.id);
}
}
return ok;
}
PeersHandler::PeersHandler(StateTokenServer* sts, RsNotify* notify, RsPeers *peers, RsMsgs* msgs):
mStateTokenServer(sts),
mNotify(notify),
mRsPeers(peers), mRsMsgs(msgs),
mMtx("PeersHandler Mutex")
{
mNotify->registerNotifyClient(this);
mStateTokenServer->registerTickClient(this);
addResourceHandler("*", this, &PeersHandler::handleWildcard);
addResourceHandler("examine_cert", this, &PeersHandler::handleExamineCert);
}
PeersHandler::~PeersHandler()
{
mNotify->unregisterNotifyClient(this);
mStateTokenServer->unregisterTickClient(this);
}
void PeersHandler::notifyListChange(int list, int type)
{
RsStackMutex stack(mMtx); /********** STACK LOCKED MTX ******/
if(list == NOTIFY_LIST_FRIENDS)
{
mStateTokenServer->discardToken(mStateToken);
mStateToken = mStateTokenServer->getNewToken();
}
}
void PeersHandler::tick()
{
std::list<RsPeerId> online;
mRsPeers->getOnlineList(online);
if(!std::equal(online.begin(), online.end(), mOnlinePeers.begin()))
{
mOnlinePeers = online;
RsStackMutex stack(mMtx); /********** STACK LOCKED MTX ******/
mStateTokenServer->discardToken(mStateToken);
mStateToken = mStateTokenServer->getNewToken();
}
}
void PeersHandler::handleWildcard(Request &req, Response &resp)
{
bool ok = false;
if(!req.mPath.empty())
{
std::string str = req.mPath.top();
req.mPath.pop();
if(str != "")
{
// assume the path element is a peer id
// sometimes it is a peer id for location info
// another time it is a pgp id
// this will confuse the client developer
if(!req.mPath.empty() && req.mPath.top() == "avatar_image")
{
// the avatar image
// better have this extra, else have to load all avatar images
// only to see who is online
unsigned char *data = NULL ;
int size = 0 ;
mRsMsgs->getAvatarData(RsPeerId(str),data,size) ;
std::vector<uint8_t> avatar(data, data+size);
delete[] data;
resp.mDataStream << avatar;
}
else if(!req.mPath.empty() && req.mPath.top() == "delete")
{
mRsPeers->removeFriend(RsPgpId(str));
}
else
{
std::list<RsGroupInfo> grpInfo;
mRsPeers->getGroupInfoList(grpInfo);
RsPeerDetails details;
ok &= mRsPeers->getPeerDetails(RsPeerId(str), details);
ok = peerInfoToStream(resp.mDataStream, details, mRsPeers, grpInfo);
}
}
}
else
{
// no more path element
if(req.isGet())
{
// list all peers
ok = true;
std::list<RsPgpId> identities;
ok &= mRsPeers->getGPGAcceptedList(identities);
std::list<RsPeerId> peers;
ok &= mRsPeers->getFriendList(peers);
std::list<RsGroupInfo> grpInfo;
mRsPeers->getGroupInfoList(grpInfo);
std::vector<RsPeerDetails> detailsVec;
for(std::list<RsPeerId>::iterator lit = peers.begin(); lit != peers.end(); ++lit)
{
RsPeerDetails details;
ok &= mRsPeers->getPeerDetails(*lit, details);
detailsVec.push_back(details);
}
for(std::list<RsPgpId>::iterator lit = identities.begin(); lit != identities.end(); ++lit)
{
StreamBase& itemStream = resp.mDataStream.getStreamToMember();
itemStream << makeKeyValueReference("pgp_id", *lit);
itemStream << makeKeyValue("name", mRsPeers->getGPGName(*lit));
StreamBase& locationStream = itemStream.getStreamToMember("locations");
for(std::vector<RsPeerDetails>::iterator vit = detailsVec.begin(); vit != detailsVec.end(); ++vit)
{
if(vit->gpg_id == *lit)
peerInfoToStream(locationStream.getStreamToMember(),*vit, mRsPeers, grpInfo);
}
}
resp.mStateToken = getCurrentStateToken();
}
else if(req.isPut())
{
std::string cert_string;
req.mStream << makeKeyValueReference("cert_string", cert_string);
RsPeerId peer_id;
RsPgpId pgp_id;
std::string error_string;
if(mRsPeers->loadCertificateFromString(cert_string, peer_id, pgp_id, error_string)
&& mRsPeers->addFriend(peer_id, pgp_id))
{
ok = true;
resp.mDataStream << makeKeyValueReference("pgp_id", pgp_id);
resp.mDataStream << makeKeyValueReference("peer_id", peer_id);
}
else
{
resp.mDebug << "Error: failed to add peer" << std::endl;
resp.mDebug << error_string << std::endl;
}
}
}
if(ok)
{
resp.setOk();
}
else
{
resp.setFail();
}
}
void PeersHandler::handleExamineCert(Request &req, Response &resp)
{
std::string cert_string;
req.mStream << makeKeyValueReference("cert_string", cert_string);
RsPeerDetails details;
uint32_t error_code;
if(mRsPeers->loadDetailsFromStringCert(cert_string, details, error_code))
{
peerDetailsToStream(resp.mDataStream, details);
resp.setOk();
}
else
{
resp.setFail("failed to load certificate");
}
}
StateToken PeersHandler::getCurrentStateToken()
{
RsStackMutex stack(mMtx); /********** STACK LOCKED MTX ******/
if(mStateToken.isNull())
mStateToken = mStateTokenServer->getNewToken();
return mStateToken;
}
} // namespace resource_api

View File

@ -0,0 +1,43 @@
#pragma once
#include "ResourceRouter.h"
#include "StateTokenServer.h"
#include <retroshare/rsnotify.h>
#include <util/rsthreads.h>
class RsPeers;
class RsMsgs;
namespace resource_api
{
class PeersHandler: public ResourceRouter, NotifyClient, Tickable
{
public:
PeersHandler(StateTokenServer* sts, RsNotify* notify, RsPeers* peers, RsMsgs* msgs);
virtual ~PeersHandler();
// from NotifyClient
// note: this may get called from foreign threads
virtual void notifyListChange(int list, int type); // friends list change
// from Tickable
virtual void tick();
private:
void handleWildcard(Request& req, Response& resp);
void handleExamineCert(Request& req, Response& resp);
// a helper which ensures proper mutex locking
StateToken getCurrentStateToken();
StateTokenServer* mStateTokenServer;
RsNotify* mNotify;
RsPeers* mRsPeers;
RsMsgs* mRsMsgs; // required for avatar data
std::list<RsPeerId> mOnlinePeers;
RsMutex mMtx;
StateToken mStateToken; // mutex protected
};
} // namespace resource_api

View File

@ -0,0 +1,67 @@
New programming interface for Retroshare
========================================
* access to libretroshare for webinterfaces, QML and scripting
* client - server architecture.
* network friendly: transport messages over high latency and low bandwidth networks
* multiple clients: can use scripting and webinterface at the same time
* simple packet format: no special serialiser required
* simple protocol: one request creates one response. A requets does not depend on a previous request.
* automatic state change propagation: if a resource on the server changes, the clients will get notified
* no shared state: Client and server don't have to track what they send each other.
* works with all programming languages
How does it work?
-----------------
- Client sends a request: adress of a resource and optional parameters encoded as JSON
{
"method": "get",
"resource": ["peers"],
}
- Server sends a Response:
{
"returncode": "ok",
"statetoken": "ASDF",
"data": [...]
}
- Client asks if data is still valid
{
"method": "exec",
"resource": "statetokenservice"
"data": ["ASDF", "GHJK"]
}
- Server answers Client that statetoken "ASDF" expired
{
"returncode": "ok",
"data": ["ASDF"]
}
Transport
---------
A transport protocol transports requests and responses between client and server.
* tracks message boundaries, so messages don't get merged
* may be able to handle concurrent requests with out of order delivery of responses
* knows to which request a response belongs to
Transport could do encryption and authentication with a standardized protocol like SSH or TLS.
Ideas:
* request id + length + request data -> SSH -> TCP -> ...
* Websockets
* Retroshare Items
* Unix domain sockets
Currently only unencrypted http is implemented. libmicrohttpd (short MHD) is used as http server.
Can use a proxy to add TLS encryption.
Message encoding
----------------
Currently JSON, because it is already available in JavaScript and QML.
Other key-value encodings could be used as well.
Read more about basic data types of different languages (JavaScript, QML, Lua, C++) in ./ApiServer.cpp.

View File

@ -0,0 +1,57 @@
#include "ResourceRouter.h"
namespace resource_api
{
class TestResource: public ResourceRouter
{
public:
TestResource()
{
addResourceHandler("eins", this, &TestResource::eins);
}
ResponseTask* eins(Request& req, Response& resp)
{
return 0;
}
};
ResourceRouter::~ResourceRouter()
{
std::vector<std::pair<std::string, HandlerBase*> >::iterator vit;
for(vit = mHandlers.begin(); vit != mHandlers.end(); vit++)
{
delete vit->second;
}
}
ResponseTask* ResourceRouter::handleRequest(Request& req, Response& resp)
{
std::vector<std::pair<std::string, HandlerBase*> >::iterator vit;
if(!req.mPath.empty())
{
for(vit = mHandlers.begin(); vit != mHandlers.end(); vit++)
{
if(vit->first == req.mPath.top())
{
req.mPath.pop();
return vit->second->handleRequest(req, resp);
}
}
}
// not found, search for wildcard handler
for(vit = mHandlers.begin(); vit != mHandlers.end(); vit++)
{
if(vit->first == "*")
{
// don't pop the path component, because it may contain usefull info for the wildcard handler
//req.mPath.pop();
return vit->second->handleRequest(req, resp);
}
}
return 0;
}
} // namespace resource_api

View File

@ -0,0 +1,76 @@
#pragma once
#include "ApiTypes.h"
namespace resource_api
{
// a base class for routing requests to handler methods
class ResourceRouter
{
public:
virtual ~ResourceRouter();
// can return NULL, if the request was processed
// if the Response can not be created immediately,
// then return a object which implements the ResponseTask interface
ResponseTask* handleRequest(Request& req, Response& resp);
template <class T>
void addResourceHandler(std::string name, T* instance, ResponseTask* (T::*callback)(Request& req, Response& resp));
template <class T>
void addResourceHandler(std::string name, T* instance, void (T::*callback)(Request& req, Response& resp));
private:
class HandlerBase
{
public:
virtual ResponseTask* handleRequest(Request& req, Response& resp) = 0;
};
template <class T>
class Handler: public HandlerBase
{
public:
virtual ResponseTask* handleRequest(Request &req, Response &resp)
{
return (instance->*method)(req, resp);
}
T* instance;
ResponseTask* (T::*method)(Request& req, Response& resp);
};
template <class T>
class InstantResponseHandler: public HandlerBase
{
public:
virtual ResponseTask* handleRequest(Request &req, Response &resp)
{
(instance->*method)(req, resp);
return 0;
}
T* instance;
void (T::*method)(Request& req, Response& resp);
};
std::vector<std::pair<std::string, HandlerBase*> > mHandlers;
};
// the advantage of this approach is:
// the method name is arbitrary, one class can have many different handler methods
// with raw objects the name of the handler method would be fixed, and we would need one class for every handler
// the downside is complicated template magic
template <class T>
void ResourceRouter::addResourceHandler(std::string name, T* instance, ResponseTask* (T::*callback)(Request& req, Response& resp))
{
Handler<T>* handler = new Handler<T>();
handler->instance = instance;
handler->method = callback;
mHandlers.push_back(std::make_pair(name, handler));
}
template <class T>
void ResourceRouter::addResourceHandler(std::string name, T* instance, void (T::*callback)(Request& req, Response& resp))
{
InstantResponseHandler<T>* handler = new InstantResponseHandler<T>();
handler->instance = instance;
handler->method = callback;
mHandlers.push_back(std::make_pair(name, handler));
}
} // namespace resource_api

View File

@ -0,0 +1,325 @@
#include "RsControlModule.h"
#include <sstream>
#include <unistd.h>
#include <retroshare/rsinit.h>
#include <retroshare/rsiface.h>
#include "api/ApiServer.h"
#include "api/Operators.h"
#include "api/StateTokenServer.h"
#include "GetPluginInterfaces.h"
namespace resource_api{
RsControlModule::RsControlModule(int argc, char **argv, StateTokenServer* sts, ApiServer *apiserver):
mStateTokenServer(sts),
mApiServer(apiserver),
mExitFlagMtx("RsControlModule::mExitFlagMtx"),
mProcessShouldExit(false),
mDataMtx("RsControlModule::mDataMtx"),
mRunState(WAITING_INIT),
mAutoLoginNextTime(false),
mWantPassword(false)
{
mStateToken = sts->getNewToken();
this->argc = argc;
this->argv = argv;
// start worker thread
start();
addResourceHandler("runstate", this, &RsControlModule::handleRunState);
addResourceHandler("identities", this, &RsControlModule::handleIdentities);
addResourceHandler("locations", this, &RsControlModule::handleLocations);
addResourceHandler("password", this, &RsControlModule::handlePassword);
addResourceHandler("login", this, &RsControlModule::handleLogin);
addResourceHandler("shutdown", this, &RsControlModule::handleShutdown);
}
RsControlModule::~RsControlModule()
{
join();
}
bool RsControlModule::processShouldExit()
{
RsStackMutex stack(mExitFlagMtx);
return mProcessShouldExit;
}
bool RsControlModule::askForPassword(const std::string &key_details, bool prev_is_bad, std::string &password)
{
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
mWantPassword = true;
mKeyName = key_details;
mPassword = "";
mStateTokenServer->replaceToken(mStateToken);
}
bool wait = true;
while(wait)
{
usleep(5*1000);
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
wait = mWantPassword;
if(!wait && mPassword != "")
{
password = mPassword;
mPassword = "";
mWantPassword = false;
mStateTokenServer->replaceToken(mStateToken);
return true;
}
}
return false;
}
void RsControlModule::run()
{
std::cerr << "RsControlModule: initialising libretroshare..." << std::endl;
RsInit::InitRsConfig();
int initResult = RsInit::InitRetroShare(argc, argv, true);
if (initResult < 0) {
std::cerr << "RsControlModule: FATAL ERROR, initialising libretroshare FAILED." << std::endl;
/* Error occured */
std::stringstream ss;
switch (initResult) {
case RS_INIT_AUTH_FAILED:
ss << "RsInit::InitRetroShare AuthGPG::InitAuth failed" << std::endl;
break;
default:
/* Unexpected return code */
ss << "RsInit::InitRetroShare unexpected return code " << initResult << std::endl;
break;
}
// FATAL ERROR, we can't recover from this. Just send the message to the user.
setRunState(FATAL_ERROR, ss.str());
return;
}
// This is needed to allocate rsNotify, so that it can be used to ask for PGP passphrase
RsControl::earlyInitNotificationSystem();
rsNotify->registerNotifyClient(this);
bool login_ok = false;
while(!login_ok)
{
// skip account selection if autologin is available
if(initResult != RS_INIT_HAVE_ACCOUNT)
setRunState(WAITING_ACCOUNT_SELECT);
// wait for login request
bool auto_login = false;
bool wait_for_account_select = (initResult != RS_INIT_HAVE_ACCOUNT);
while(wait_for_account_select && !processShouldExit())
{
usleep(5*1000);
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
wait_for_account_select = mLoadPeerId.isNull();
auto_login = mAutoLoginNextTime;
if(!wait_for_account_select)
{
wait_for_account_select = !RsAccounts::SelectAccount(mLoadPeerId);
if(wait_for_account_select)
setRunState(WAITING_ACCOUNT_SELECT);
}
}
if(processShouldExit())
return;
bool autoLogin = (initResult == RS_INIT_HAVE_ACCOUNT) | auto_login;
std::string lockFile;
int retVal = RsInit::LockAndLoadCertificates(autoLogin, lockFile);
std::string error_string;
switch (retVal) {
case 0:
login_ok = true;
break;
case 1:
error_string = "Another RetroShare using the same profile is "
"already running on your system. Please close "
"that instance first\n Lock file:\n" + lockFile;
break;
case 2:
error_string = "An unexpected error occurred when Retroshare "
"tried to acquire the single instance lock\n Lock file:\n"
+ lockFile;
break;
case 3:
error_string = "Login Failure: Maybe password is wrong";
break;
default:
std::cerr << "RsControlModule::run() LockAndLoadCertificates failed. Unexpected switch value: " << retVal << std::endl;
break;
}
}
setRunState(WAITING_STARTUP);
std::cerr << "RsControlModule: login ok, starting Retroshare worker threads..." << std::endl;
RsControl::instance() -> StartupRetroShare();
std::cerr << "RsControlModule: loading main resource api modules..." << std::endl;
RsPlugInInterfaces ifaces;
getPluginInterfaces(ifaces);
mApiServer->loadMainModules(ifaces);
std::cerr << "RsControlModule: Retroshare is up and running. Enjoy!" << std::endl;
setRunState(RUNNING_OK);
while(!processShouldExit())
{
usleep(5*1000);
}
std::cerr << "RsControlModule: stopping Retroshare..." << std::endl;
RsControl::instance() -> rsGlobalShutDown();
std::cerr << "RsControlModule: Retroshare stopped. Bye!" << std::endl;
}
void RsControlModule::handleRunState(Request &req, Response &resp)
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
std::string state;
switch(mRunState)
{
case WAITING_INIT:
state = "waiting_init";
break;
case FATAL_ERROR:
state = "fatal_error";
break;
case WAITING_ACCOUNT_SELECT:
state = "waiting_account_select";
break;
case WAITING_STARTUP:
state = "waiting_startup";
break;
case RUNNING_OK:
state = "running_ok";
break;
default:
state = "error_should_not_happen_this_is_a_bug";
}
resp.mDataStream << makeKeyValueReference("runstate", state);
resp.mStateToken = mStateToken;
resp.setOk();
}
void RsControlModule::handleIdentities(Request &req, Response &resp)
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
if(mRunState == WAITING_INIT || mRunState == FATAL_ERROR)
{
resp.setFail("Retroshare is not initialised. Operation not possible.");
return;
}
std::list<RsPgpId> pgp_ids;
RsAccounts::GetPGPLogins(pgp_ids);
resp.mDataStream.getStreamToMember();
for(std::list<RsPgpId>::iterator lit = pgp_ids.begin(); lit != pgp_ids.end(); ++lit)
{
std::string name;
std::string email;
if(RsAccounts::GetPGPLoginDetails(*lit, name, email))
resp.mDataStream.getStreamToMember()
<< makeKeyValueReference("id", *lit)
<< makeKeyValueReference("pgp_id", *lit)
<< makeKeyValueReference("name", name);
}
resp.mStateToken = mStateToken;
resp.setOk();
}
void RsControlModule::handleLocations(Request &req, Response &resp)
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
if(mRunState == WAITING_INIT || mRunState == FATAL_ERROR)
{
resp.setFail("Retroshare is not initialised. Operation not possible.");
return;
}
RsPeerId preferedId;
RsAccounts::GetPreferredAccountId(preferedId);
std::list<RsPeerId> peer_ids;
RsAccounts::GetAccountIds(peer_ids);
resp.mDataStream.getStreamToMember();
for(std::list<RsPeerId>::iterator lit = peer_ids.begin(); lit != peer_ids.end(); ++lit)
{
bool preferred = preferedId==*lit;
RsPgpId pgp_id;
std::string pgp_name, pgp_mail, location_name;
if(RsAccounts::GetAccountDetails(*lit, pgp_id, pgp_name, pgp_mail, location_name))
resp.mDataStream.getStreamToMember()
<< makeKeyValueReference("id", *lit)
<< makeKeyValueReference("pgp_id", pgp_id)
<< makeKeyValueReference("peer_id", *lit)
<< makeKeyValueReference("name", pgp_name)
<< makeKeyValueReference("location", location_name)
<< makeKeyValueReference("preferred", preferred);
}
resp.mStateToken = mStateToken;
resp.setOk();
}
void RsControlModule::handlePassword(Request &req, Response &resp)
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
std::string passwd;
req.mStream << makeKeyValueReference("password", passwd);
if(passwd != "" && mWantPassword)
{
// client sends password
mPassword = passwd;
mWantPassword = false;
mStateTokenServer->replaceToken(mStateToken);
}
resp.mDataStream
<< makeKeyValueReference("want_password", mWantPassword)
<< makeKeyValueReference("key_name", mKeyName);
resp.mStateToken = mStateToken;
resp.setOk();
}
void RsControlModule::handleLogin(Request &req, Response &resp)
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
if(mRunState != WAITING_ACCOUNT_SELECT)
{
resp.setFail("Operation not allowed in this runstate. Login is only allowed rigth after initialisation.");
return;
}
req.mStream << makeKeyValueReference("id", mLoadPeerId)
<< makeKeyValueReference("autologin", mAutoLoginNextTime);
resp.setOk();
}
void RsControlModule::handleShutdown(Request &req, Response &resp)
{
RsStackMutex stack(mExitFlagMtx); // ********** LOCKED **********
mProcessShouldExit = true;
resp.setOk();
}
void RsControlModule::setRunState(RunState s, std::string errstr)
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
mRunState = s;
mLastErrorString = errstr;
mStateTokenServer->replaceToken(mStateToken);
}
} // namespace resource_api

View File

@ -0,0 +1,80 @@
#pragma once
#include <util/rsthreads.h>
#include <retroshare/rsnotify.h>
#include "api/ResourceRouter.h"
namespace resource_api{
class StateTokenServer;
class ApiServer;
// resource api module to control accounts, startup and shutdown of retroshare
// - this module handles everything, not things are required from outside
// - exception: users of this module have to create an api server and register this module
// tasks:
// - show, import, export and create private pgp keys
// - show existing and create new locations
// - load certificate, startup retroshare
// - handle password callback
// - confirm plugin loading
// - shutdown retroshare
class RsControlModule: public ResourceRouter, NotifyClient,
private RsThread
{
public:
// ApiServer will be called once RS is started, to load additional api modules
RsControlModule(int argc, char **argv, StateTokenServer* sts, ApiServer* apiserver);
~RsControlModule();
// returns true if the process should terminate
bool processShouldExit();
// from NotifyClient
virtual bool askForPassword(const std::string& key_details, bool prev_is_bad , std::string& password);
protected:
// from RsThread
// wee need a thread to call into things which block like askForPassword()
virtual void run();
private:
enum RunState { WAITING_INIT, FATAL_ERROR, WAITING_ACCOUNT_SELECT, WAITING_STARTUP, RUNNING_OK};
void handleRunState(Request& req, Response& resp);
void handleIdentities(Request& req, Response& resp);
void handleLocations(Request& req, Response& resp);
void handlePassword(Request& req, Response& resp);
void handleLogin(Request& req, Response& resp);
void handleShutdown(Request& req, Response& resp);
void setRunState(RunState s, std::string errstr = "");
// for startup
int argc;
char **argv;
StateTokenServer* const mStateTokenServer;
ApiServer* const mApiServer;
RsMutex mExitFlagMtx;
bool mProcessShouldExit;
RsMutex mDataMtx;
StateToken mStateToken; // one state token for everything, to make life easier
RunState mRunState;
std::string mLastErrorString;
// id of the account to load
// null when no account was selected
RsPeerId mLoadPeerId;
bool mAutoLoginNextTime;
// to notify that a password callback is waiting
// to answer the request, clear the flag and set the password
bool mWantPassword;
std::string mKeyName;
std::string mPassword;
};
} // namespace resource_api

View File

@ -0,0 +1,89 @@
#include "ServiceControlHandler.h"
#include "retroshare/rsservicecontrol.h"
#include "Operators.h"
namespace resource_api
{
// maybe move to another place later
// need more generic operators for list, vector, map
template<class T>
void setToStream(StreamBase& stream, std::set<T>& set)
{
if(stream.serialise())
{
for(typename std::set<T>::iterator sit = set.begin(); sit != set.end(); sit++)
{
T item = *sit;
stream << makeValueReference(item);
}
}
else
{
while(stream.hasMore())
{
T item;
stream << makeValueReference(item);
set.insert(item);
}
}
}
void servicePermissionToStream(StreamBase& stream, RsServicePermissions& perm)
{
stream << makeKeyValueReference("service_id", perm.mServiceId)
<< makeKeyValueReference("service_name", perm.mServiceName)
<< makeKeyValueReference("default_allowed", perm.mDefaultAllowed)
;
setToStream(stream.getStreamToMember("peers_allowed"), perm.mPeersAllowed);
setToStream(stream.getStreamToMember("peers_denied"), perm.mPeersDenied);
}
ServiceControlHandler::ServiceControlHandler(RsServiceControl* control):
mRsServiceControl(control)
{
addResourceHandler("*", this, &ServiceControlHandler::handleWildcard);
}
void ServiceControlHandler::handleWildcard(Request &req, Response &resp)
{
bool ok = false;
if(!req.mPath.empty())
{
}
else
{
// no more path element
if(req.isGet())
{
// list all peers
ok = true;
RsPeerServiceInfo psi;
ok &= mRsServiceControl->getOwnServices(psi);
for(std::map<uint32_t, RsServiceInfo>::iterator mit = psi.mServiceList.begin(); mit != psi.mServiceList.end(); mit++)
{
RsServicePermissions perms;
ok &= mRsServiceControl->getServicePermissions(mit->first, perms);
if(ok)
{
servicePermissionToStream(resp.mDataStream.getStreamToMember(), perms);
}
}
}
else if(req.isPut())
{
}
}
if(ok)
{
resp.setOk();
}
else
{
resp.setFail();
}
}
} // namespace resource_api

View File

@ -0,0 +1,20 @@
#pragma once
#include "ApiTypes.h"
#include "ResourceRouter.h"
class RsServiceControl;
namespace resource_api
{
class ServiceControlHandler: public ResourceRouter
{
public:
ServiceControlHandler(RsServiceControl* control);
private:
RsServiceControl* mRsServiceControl;
void handleWildcard(Request& req, Response& resp);
};
} // namespace resource_api

View File

@ -0,0 +1,150 @@
#include "StateTokenServer.h"
#include <algorithm>
namespace resource_api
{
// maybe it would be good to make this part of state token or friend, to be able to directly access the value
StreamBase& operator <<(StreamBase& left, KeyValueReference<StateToken> kv)
{
if(left.serialise())
{
// have to make a variable, to be able to pass it by reference
// (cant pass return value of a function by refernce to another function)
int value = kv.value.getValue();
left << makeKeyValueReference(kv.key, value);
}
else
{
int value;
left << makeKeyValueReference(kv.key, value);
kv.value = StateToken(value);
}
return left;
}
StreamBase& operator<<(StreamBase& left, StateToken& token)
{
if(left.serialise())
{
// have to make a variable, to be able to pass it by reference
// (cant pass return value of a function by refernce to another function)
int value = token.getValue();
left << value;
}
else
{
int value;
left << value;
token = StateToken(value);
}
return left;
}
bool operator==(const StateToken& left, const StateToken& right)
{
if(left.getValue() == right.getValue())
{
return true;
}
else
{
return false;
}
}
StateTokenServer::StateTokenServer():
mMtx("StateTokenServer mMtx"),
mNextToken(1),
mClientsMtx("StateTokenServer mClientsMtx")
{
addResourceHandler("*", this, &StateTokenServer::handleWildcard);
}
StateToken StateTokenServer::getNewToken()
{
RsStackMutex stack(mMtx); /********** STACK LOCKED MTX ******/
return locked_getNewToken();
}
void StateTokenServer::discardToken(StateToken token)
{
RsStackMutex stack(mMtx); /********** STACK LOCKED MTX ******/
locked_discardToken(token);
}
void StateTokenServer::replaceToken(StateToken &token)
{
RsStackMutex stack(mMtx); /********** STACK LOCKED MTX ******/
locked_discardToken(token);
token = locked_getNewToken();
}
void StateTokenServer::registerTickClient(Tickable *c)
{
// extra service: tick it to let it init its ticking stuff
c->tick();
// avoid double registration
unregisterTickClient(c);
RsStackMutex stack(mClientsMtx); /********** STACK LOCKED MTX ***********/
mTickClients.push_back(c);
}
void StateTokenServer::unregisterTickClient(Tickable *c)
{
RsStackMutex stack(mClientsMtx); /********** STACK LOCKED MTX ***********/
std::vector<Tickable*>::iterator vit = std::find(mTickClients.begin(), mTickClients.end(), c);
if(vit != mTickClients.end())
mTickClients.erase(vit);
}
void StateTokenServer::handleWildcard(Request &req, Response &resp)
{
{
RsStackMutex stack(mClientsMtx); /********** STACK LOCKED MTX ***********/
for(std::vector<Tickable*>::iterator vit = mTickClients.begin(); vit != mTickClients.end(); ++vit)
{
(*vit)->tick();
}
}
RsStackMutex stack(mMtx); /********** STACK LOCKED MTX ******/
// want to lookpup many tokens at once, return a list of invalid tokens
// TODO: make generic list serialiser/deserialiser
while(req.mStream.hasMore())
{
StateToken token;
req.mStream << token;
// lookup if token is valid
if(std::find(mValidTokens.begin(), mValidTokens.end(), token) == mValidTokens.end())
{
// if invalid, add to response list
resp.mDataStream << token;
}
}
resp.setOk();
}
StateToken StateTokenServer::locked_getNewToken()
{
StateToken token(mNextToken);
mValidTokens.push_back(token);
mNextToken++;
if(mNextToken == 0) // 0 is a reserved value, don't ever use it
mNextToken = 1;
return token;
}
void StateTokenServer::locked_discardToken(StateToken token)
{
std::vector<StateToken>::iterator toDelete = std::find(mValidTokens.begin(), mValidTokens.end(), token);
if(toDelete != mValidTokens.end())
{
mValidTokens.erase(toDelete);
}
}
} // namespace resource_api

View File

@ -0,0 +1,69 @@
#pragma once
#include <util/rsthreads.h>
#include "ResourceRouter.h"
namespace resource_api
{
//class StreamBase;
//class StateToken;
// serialiser/deserialiser (depends on stream type)
// for single value
StreamBase& operator <<(StreamBase& left, KeyValueReference<StateToken> kv);
// for lists
StreamBase& operator <<(StreamBase& left, StateToken& token);
bool operator ==(const StateToken& left, const StateToken& right);
class Tickable{
public:
virtual void tick() = 0;
};
class StateTokenServer: public ResourceRouter
{
public:
StateTokenServer();
// thread safe
// this allows tokens to be created and destroyed from arbitrary threads
StateToken getNewToken();
void discardToken(StateToken token);
// discard the token and fill in a new one
void replaceToken(StateToken& token);
void registerTickClient(Tickable* c);
void unregisterTickClient(Tickable* c);
private:
void handleWildcard(Request& req, Response& resp);
StateToken locked_getNewToken();
void locked_discardToken(StateToken token);
RsMutex mMtx;
uint32_t mNextToken;
// not sure what the most efficient data structure for simple token storage is
// have to:
// - add elements
// - remove elements by value
// - store many values
// vector: expensive token erase, could make this better with a invalidate flag
// and periodic cleanup
// list: lots of overhead for pointers
// a set would offer cheap lookup
// we have to lookup often, so maybe this would be an option
// have to see where the bottleneck is in practice
// idea: invalidate all tokens after x minutes/hours, to limit the range of the token values
// then store the token states in a circular bitbuffer
std::vector<StateToken> mValidTokens;
// classes which want to be ticked
RsMutex mClientsMtx; // needs extra mutex, because clients may call back to modify get/delete tokens
std::vector<Tickable*> mTickClients;
};
} // namespace resource_api

View File

@ -0,0 +1,177 @@
#include "TransfersHandler.h"
#include "Operators.h"
#include <algorithm>
namespace resource_api
{
TransfersHandler::TransfersHandler(StateTokenServer *sts, RsFiles *files):
mStateTokenServer(sts), mFiles(files), mLastUpdateTS(0)
{
addResourceHandler("*", this, &TransfersHandler::handleWildcard);
addResourceHandler("downloads", this, &TransfersHandler::handleDownloads);
addResourceHandler("control_download", this, &TransfersHandler::handleControlDownload);
mStateToken = mStateTokenServer->getNewToken();
mStateTokenServer->registerTickClient(this);
}
TransfersHandler::~TransfersHandler()
{
mStateTokenServer->unregisterTickClient(this);
}
const int UPDATE_PERIOD_SECONDS = 5;
void TransfersHandler::tick()
{
if(time(0) > (mLastUpdateTS + UPDATE_PERIOD_SECONDS))
mStateTokenServer->replaceToken(mStateToken);
// extra check: was the list of files changed?
// if yes, replace state token immediately
std::list<RsFileHash> dls;
mFiles->FileDownloads(dls);
// there is no guarantee of the order
// so have to sort before comparing the lists
dls.sort();
if(!std::equal(dls.begin(), dls.end(), mDownloadsAtLastCheck.begin()))
{
mDownloadsAtLastCheck.swap(dls);
mStateTokenServer->replaceToken(mStateToken);
}
}
void TransfersHandler::handleWildcard(Request &req, Response &resp)
{
}
void TransfersHandler::handleControlDownload(Request &req, Response &resp)
{
mStateTokenServer->replaceToken(mStateToken);
RsFileHash hash;
std::string action;
req.mStream << makeKeyValueReference("action", action);
if(action == "begin")
{
std::string fname;
double size;
req.mStream << makeKeyValueReference("name", fname);
req.mStream << makeKeyValueReference("size", size);
req.mStream << makeKeyValueReference("hash", hash);
std::list<RsPeerId> scrIds;
bool ok = req.mStream.isOK();
if(ok)
ok = mFiles->FileRequest(fname, hash, size, "", RS_FILE_REQ_ANONYMOUS_ROUTING, scrIds);
if(ok)
resp.setOk();
else
resp.setFail("something went wrong. are all fields filled in? is the file already downloaded?");
return;
}
req.mStream << makeKeyValueReference("id", hash);
if(!req.mStream.isOK())
{
resp.setFail("error: could not deserialise the request");
return;
}
bool ok = false;
bool handled = false;
if(action == "pause")
{
handled = true;
ok = mFiles->FileControl(hash, RS_FILE_CTRL_PAUSE);
}
if(action == "start")
{
handled = true;
ok = mFiles->FileControl(hash, RS_FILE_CTRL_START);
}
if(action == "check")
{
handled = true;
ok = mFiles->FileControl(hash, RS_FILE_CTRL_FORCE_CHECK);
}
if(action == "cancel")
{
handled = true;
ok = mFiles->FileCancel(hash);
}
if(ok)
resp.setOk();
else
resp.setFail("something went wrong. not sure what or why.");
if(handled)
return;
resp.setFail("error: action not handled");
}
void TransfersHandler::handleDownloads(Request &req, Response &resp)
{
tick();
resp.mStateToken = mStateToken;
resp.mDataStream.getStreamToMember();
for(std::list<RsFileHash>::iterator lit = mDownloadsAtLastCheck.begin();
lit != mDownloadsAtLastCheck.end(); ++lit)
{
FileInfo fi;
if(mFiles->FileDetails(*lit, RS_FILE_HINTS_DOWNLOAD, fi))
{
StreamBase& stream = resp.mDataStream.getStreamToMember();
stream << makeKeyValueReference("id", fi.hash)
<< makeKeyValueReference("hash", fi.hash)
<< makeKeyValueReference("name", fi.fname);
double size = fi.size;
double transfered = fi.transfered;
stream << makeKeyValueReference("size", size)
<< makeKeyValueReference("transfered", transfered);
std::string dl_status;
/*
const uint32_t FT_STATE_FAILED = 0x0000 ;
const uint32_t FT_STATE_OKAY = 0x0001 ;
const uint32_t FT_STATE_WAITING = 0x0002 ;
const uint32_t FT_STATE_DOWNLOADING = 0x0003 ;
const uint32_t FT_STATE_COMPLETE = 0x0004 ;
const uint32_t FT_STATE_QUEUED = 0x0005 ;
const uint32_t FT_STATE_PAUSED = 0x0006 ;
const uint32_t FT_STATE_CHECKING_HASH = 0x0007 ;
*/
switch(fi.downloadStatus)
{
case FT_STATE_FAILED:
dl_status = "failed";
break;
case FT_STATE_OKAY:
dl_status = "okay";
break;
case FT_STATE_WAITING:
dl_status = "waiting";
break;
case FT_STATE_DOWNLOADING:
dl_status = "downloading";
break;
case FT_STATE_COMPLETE:
dl_status = "complete";
break;
case FT_STATE_QUEUED:
dl_status = "queued";
break;
case FT_STATE_PAUSED:
dl_status = "paused";
break;
case FT_STATE_CHECKING_HASH:
dl_status = "checking";
break;
default:
dl_status = "error_unknown";
}
stream << makeKeyValueReference("download_status", dl_status);
}
}
resp.setOk();
}
} // namespace resource_api

View File

@ -0,0 +1,34 @@
#pragma once
#pragma once
#include "ResourceRouter.h"
#include "StateTokenServer.h"
#include <retroshare/rsfiles.h>
namespace resource_api
{
class TransfersHandler: public ResourceRouter, Tickable
{
public:
TransfersHandler(StateTokenServer* sts, RsFiles* files);
virtual ~TransfersHandler();
// from Tickable
virtual void tick();
private:
void handleWildcard(Request& req, Response& resp);
void handleControlDownload(Request& req, Response& resp);
void handleDownloads(Request& req, Response& resp);
StateTokenServer* mStateTokenServer;
RsFiles* mFiles;
StateToken mStateToken;
time_t mLastUpdateTS;
std::list<RsFileHash> mDownloadsAtLastCheck;
};
} // namespace resource_api

813
libresapi/src/api/json.cpp Normal file
View File

@ -0,0 +1,813 @@
#include "json.h"
#include <stdlib.h>
#include <string>
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <string.h>
#include <functional>
#include <cctype>
#include <stack>
#ifndef WIN32
#define _stricmp strcasecmp
#endif
#ifdef _MSC_VER
#define snprintf sprintf_s
#endif
using namespace json;
namespace json
{
enum StackDepthType
{
InObject,
InArray
};
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper functions
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static std::string Trim(const std::string& str)
{
std::string s = str;
// remove white space in front
s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace))));
// remove trailing white space
s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
return s;
}
// Finds the position of the first " character that is NOT preceeded immediately by a \ character.
// In JSON, \" is valid and has a different meaning than the escaped " character.
static size_t GetQuotePos(const std::string& str, size_t start_pos = 0)
{
bool found_slash = false;
for (size_t i = start_pos; i < str.length(); i++)
{
char c = str[i];
if ((c == '\\') && !found_slash)
{
found_slash = true;
continue;
}
else if ((c == '\"') && !found_slash)
return i;
found_slash = false;
}
return std::string::npos;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Value::Value(const Value& v) : mValueType(v.mValueType)
{
switch (mValueType)
{
case StringVal : mStringVal = v.mStringVal; break;
case IntVal : mIntVal = v.mIntVal; mFloatVal = (float)v.mIntVal; mDoubleVal = (double)v.mIntVal; break;
case FloatVal : mFloatVal = v.mFloatVal; mIntVal = (int)v.mFloatVal; mDoubleVal = (double)v.mDoubleVal; break;
case DoubleVal : mDoubleVal = v.mDoubleVal; mIntVal = (int)v.mDoubleVal; mFloatVal = (float)v.mDoubleVal; break;
case BoolVal : mBoolVal = v.mBoolVal; break;
case ObjectVal : mObjectVal = v.mObjectVal; break;
case ArrayVal : mArrayVal = v.mArrayVal; break;
default : break;
}
}
Value& Value::operator =(const Value& v)
{
if (&v == this)
return *this;
mValueType = v.mValueType;
switch (mValueType)
{
case StringVal : mStringVal = v.mStringVal; break;
case IntVal : mIntVal = v.mIntVal; mFloatVal = (float)v.mIntVal; mDoubleVal = (double)v.mIntVal; break;
case FloatVal : mFloatVal = v.mFloatVal; mIntVal = (int)v.mFloatVal; mDoubleVal = (double)v.mDoubleVal; break;
case DoubleVal : mDoubleVal = v.mDoubleVal; mIntVal = (int)v.mDoubleVal; mFloatVal = (float)v.mDoubleVal; break;
case BoolVal : mBoolVal = v.mBoolVal; break;
case ObjectVal : mObjectVal = v.mObjectVal; break;
case ArrayVal : mArrayVal = v.mArrayVal; break;
default : break;
}
return *this;
}
Value& Value::operator [](size_t idx)
{
assert(mValueType == ArrayVal);
return mArrayVal[idx];
}
const Value& Value::operator [](size_t idx) const
{
assert(mValueType == ArrayVal);
return mArrayVal[idx];
}
Value& Value::operator [](const std::string& key)
{
assert(mValueType == ObjectVal);
return mObjectVal[key];
}
Value& Value::operator [](const char* key)
{
assert(mValueType == ObjectVal);
return mObjectVal[key];
}
const Value& Value::operator [](const char* key) const
{
assert(mValueType == ObjectVal);
return mObjectVal[key];
}
const Value& Value::operator [](const std::string& key) const
{
assert(mValueType == ObjectVal);
return mObjectVal[key];
}
void Value::Clear()
{
mValueType = NULLVal;
}
size_t Value::size() const
{
if ((mValueType != ObjectVal) && (mValueType != ArrayVal))
return 1;
return mValueType == ObjectVal ? mObjectVal.size() : mArrayVal.size();
}
bool Value::HasKey(const std::string &key) const
{
assert(mValueType == ObjectVal);
return mObjectVal.HasKey(key);
}
int Value::HasKeys(const std::vector<std::string> &keys) const
{
assert(mValueType == ObjectVal);
return mObjectVal.HasKeys(keys);
}
int Value::HasKeys(const char **keys, int key_count) const
{
assert(mValueType == ObjectVal);
return mObjectVal.HasKeys(keys, key_count);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Array::Array()
{
}
Array::Array(const Array& a) : mValues(a.mValues)
{
}
Array& Array::operator =(const Array& a)
{
if (&a == this)
return *this;
Clear();
mValues = a.mValues;
return *this;
}
Value& Array::operator [](size_t i)
{
return mValues[i];
}
const Value& Array::operator [](size_t i) const
{
return mValues[i];
}
Array::ValueVector::const_iterator Array::begin() const
{
return mValues.begin();
}
Array::ValueVector::const_iterator Array::end() const
{
return mValues.end();
}
Array::ValueVector::iterator Array::begin()
{
return mValues.begin();
}
Array::ValueVector::iterator Array::end()
{
return mValues.end();
}
void Array::push_back(const Value& v)
{
mValues.push_back(v);
}
void Array::insert(size_t index, const Value& v)
{
mValues.insert(mValues.begin() + index, v);
}
size_t Array::size() const
{
return mValues.size();
}
void Array::Clear()
{
mValues.clear();
}
Array::ValueVector::iterator Array::find(const Value& v)
{
return std::find(mValues.begin(), mValues.end(), v);
}
Array::ValueVector::const_iterator Array::find(const Value& v) const
{
return std::find(mValues.begin(), mValues.end(), v);
}
bool Array::HasValue(const Value& v) const
{
return find(v) != end();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Object::Object()
{
}
Object::Object(const Object& obj) : mValues(obj.mValues)
{
}
Object& Object::operator =(const Object& obj)
{
if (&obj == this)
return *this;
Clear();
mValues = obj.mValues;
return *this;
}
Value& Object::operator [](const std::string& key)
{
return mValues[key];
}
const Value& Object::operator [](const std::string& key) const
{
ValueMap::const_iterator it = mValues.find(key);
return it->second;
}
Value& Object::operator [](const char* key)
{
return mValues[key];
}
const Value& Object::operator [](const char* key) const
{
ValueMap::const_iterator it = mValues.find(key);
return it->second;
}
Object::ValueMap::const_iterator Object::begin() const
{
return mValues.begin();
}
Object::ValueMap::const_iterator Object::end() const
{
return mValues.end();
}
Object::ValueMap::iterator Object::begin()
{
return mValues.begin();
}
Object::ValueMap::iterator Object::end()
{
return mValues.end();
}
Object::ValueMap::iterator Object::find(const std::string& key)
{
return mValues.find(key);
}
Object::ValueMap::const_iterator Object::find(const std::string& key) const
{
return mValues.find(key);
}
bool Object::HasKey(const std::string& key) const
{
return find(key) != end();
}
int Object::HasKeys(const std::vector<std::string>& keys) const
{
for (size_t i = 0; i < keys.size(); i++)
{
if (!HasKey(keys[i]))
return (int)i;
}
return -1;
}
int Object::HasKeys(const char** keys, int key_count) const
{
for (int i = 0; i < key_count; i++)
if (!HasKey(keys[i]))
return i;
return -1;
}
void Object::Clear()
{
mValues.clear();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::string SerializeArray(const Array& a);
std::string SerializeValue(const Value& v)
{
std::string str;
static const int BUFF_SZ = 500;
char buff[BUFF_SZ];
switch (v.GetType())
{
case IntVal : snprintf(buff, BUFF_SZ, "%d", (int)v); str = buff; break;
case FloatVal : snprintf(buff, BUFF_SZ, "%f", (float)v); str = buff; break;
case DoubleVal : snprintf(buff, BUFF_SZ, "%f", (double)v); str = buff; break;
case BoolVal : str = v ? "true" : "false"; break;
case NULLVal : str = "null"; break;
case ObjectVal : str = Serialize(v); break;
case ArrayVal : str = SerializeArray(v); break;
case StringVal : str = std::string("\"") + (std::string)v + std::string("\""); break;
}
return str;
}
std::string SerializeArray(const Array& a)
{
std::string str = "[";
bool first = true;
for (size_t i = 0; i < a.size(); i++)
{
const Value& v = a[i];
if (!first)
str += std::string(",");
str += SerializeValue(v);
first = false;
}
str += "]";
return str;
}
std::string json::Serialize(const Value& v)
{
std::string str;
bool first = true;
if (v.GetType() == ObjectVal)
{
str = "{";
Object obj = v.ToObject();
for (Object::ValueMap::const_iterator it = obj.begin(); it != obj.end(); it++)
{
if (!first)
str += std::string(",");
str += std::string("\"") + it->first + std::string("\":") + SerializeValue(it->second);
first = false;
}
str += "}";
}
else if (v.GetType() == ArrayVal)
{
str = "[";
Array a = v.ToArray();
for (Array::ValueVector::const_iterator it = a.begin(); it != a.end(); it++)
{
if (!first)
str += std::string(",");
str += SerializeValue(*it);
first = false;
}
str += "]";
}
//else error
return str;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static Value DeserializeArray(std::string& str, std::stack<StackDepthType>& depth_stack);
static Value DeserializeObj(const std::string& _str, std::stack<StackDepthType>& depth_stack);
static Value DeserializeInternal(const std::string& _str, std::stack<StackDepthType>& depth_stack)
{
Value v;
std::string str = Trim(_str);
if (str[0] == '{')
{
// Error: Began with a { but doesn't end with one
if (str[str.length() - 1] != '}')
return Value();
depth_stack.push(InObject);
v = DeserializeObj(str, depth_stack);
if ((v.GetType() == NULLVal) || (depth_stack.top() != InObject))
return v;
depth_stack.pop();
}
else if (str[0] == '[')
{
// Error: Began with a [ but doesn't end with one
if (str[str.length() - 1] != ']')
return Value();
depth_stack.push(InArray);
v = DeserializeArray(str, depth_stack);
if ((v.GetType() == NULLVal) || (depth_stack.top() != InArray))
return v;
depth_stack.pop();
}
else
{
// Will never get here unless _str is not valid JSON
return Value();
}
return v;
}
static size_t GetEndOfArrayOrObj(const std::string& str, std::stack<StackDepthType>& depth_stack)
{
size_t i = 1;
bool in_quote = false;
size_t original_count = depth_stack.size();
for (; i < str.length(); i++)
{
if (str[i] == '\"')
{
if (str[i - 1] != '\\')
in_quote = !in_quote;
}
else if (!in_quote)
{
if (str[i] == '[')
depth_stack.push(InArray);
else if (str[i] == '{')
depth_stack.push(InObject);
else if (str[i] == ']')
{
StackDepthType t = depth_stack.top();
if (t != InArray)
{
// expected to be closing an array but instead we're inside an object block.
// Example problem: {]}
return std::string::npos;
}
size_t count = depth_stack.size();
depth_stack.pop();
if (count == original_count)
break;
}
else if (str[i] == '}')
{
StackDepthType t = depth_stack.top();
if (t != InObject)
{
// expected to be closing an object but instead we're inside an array.
// Example problem: [}]
return std::string::npos;
}
size_t count = depth_stack.size();
depth_stack.pop();
if (count == original_count)
break;
}
}
}
return i;
}
static std::string UnescapeJSONString(const std::string& str)
{
std::string s = "";
for (int i = 0; i < str.length(); i++)
{
char c = str[i];
if ((c == '\\') && (i + 1 < str.length()))
{
int skip_ahead = 1;
unsigned int hex;
std::string hex_str;
switch (str[i+1])
{
case '"' : s.push_back('\"'); break;
case '\\': s.push_back('\\'); break;
case '/' : s.push_back('/'); break;
case 't' : s.push_back('\t'); break;
case 'n' : s.push_back('\n'); break;
case 'r' : s.push_back('\r'); break;
case 'b' : s.push_back('\b'); break;
case 'f' : s.push_back('\f'); break;
case 'u' : skip_ahead = 5;
hex_str = str.substr(i + 4, 2);
hex = (unsigned int)std::strtoul(hex_str.c_str(), NULL, 16);
s.push_back((char)hex);
break;
default: break;
}
i += skip_ahead;
}
else
s.push_back(c);
}
return Trim(s);
}
static Value DeserializeValue(std::string& str, bool* had_error, std::stack<StackDepthType>& depth_stack)
{
Value v;
*had_error = false;
str = Trim(str);
if (str.length() == 0)
return v;
if (str[0] == '[')
{
depth_stack.push(InArray);
size_t i = GetEndOfArrayOrObj(str, depth_stack);
if (i == std::string::npos)
{
*had_error = true;
return Value();
}
std::string array_str = str.substr(0, i + 1);
v = Value(DeserializeArray(array_str, depth_stack));
str = str.substr(i + 1, str.length());
}
else if (str[0] == '{')
{
depth_stack.push(InObject);
size_t i = GetEndOfArrayOrObj(str, depth_stack);
if (i == std::string::npos)
{
*had_error = true;
return Value();
}
std::string obj_str = str.substr(0, i + 1);
v = Value(DeserializeInternal(obj_str, depth_stack));
str = str.substr(i + 1, str.length());
}
else if (str[0] == '\"')
{
size_t end_quote = GetQuotePos(str, 1);
if (end_quote == std::string::npos)
{
*had_error = true;
return Value();
}
v = Value(UnescapeJSONString(str.substr(1, end_quote - 1)));
str = str.substr(end_quote + 1, str.length());
}
else
{
bool has_dot = false;
bool has_e = false;
std::string temp_val;
size_t i = 0;
for (; i < str.length(); i++)
{
if (str[i] == '.')
has_dot = true;
else if (str[i] == 'e')
has_e = true;
else if (str[i] == ']')
{
if (depth_stack.top() != InArray)
{
*had_error = true;
return Value();
}
depth_stack.pop();
}
else if (str[i] == '}')
{
if (depth_stack.top() != InObject)
{
*had_error = true;
return Value();
}
depth_stack.pop();
}
else if (str[i] == ',')
break;
if (!std::isspace(str[i]))
temp_val += str[i];
}
// store all floating point as doubles. This will also set the float and int values as well.
if (_stricmp(temp_val.c_str(), "true") == 0)
v = Value(true);
else if (_stricmp(temp_val.c_str(), "false") == 0)
v = Value(false);
else if (has_e || has_dot)
v = Value(atof(temp_val.c_str()));
else if (_stricmp(temp_val.c_str(), "null") == 0)
v = Value();
else
{
// Check if the value is beyond the size of an int and if so, store it as a double
double tmp_val = atof(temp_val.c_str());
if ((tmp_val >= (double)INT_MIN) && (tmp_val <= (double)INT_MAX))
v = Value(atoi(temp_val.c_str()));
else
v = Value(tmp_val);
}
str = str.substr(i, str.length());
}
return v;
}
static Value DeserializeArray(std::string& str, std::stack<StackDepthType>& depth_stack)
{
Array a;
bool had_error = false;
str = Trim(str);
if ((str[0] == '[') && (str[str.length() - 1] == ']'))
str = str.substr(1, str.length() - 2);
else
return Value();
while (str.length() > 0)
{
std::string tmp;
size_t i = 0;
for (; i < str.length(); i++)
{
// If we get to an object or array, parse it:
if ((str[i] == '{') || (str[i] == '['))
{
Value v = DeserializeValue(str, &had_error, depth_stack);
if (had_error)
return Value();
if (v.GetType() != NULLVal)
a.push_back(v);
break;
}
bool terminate_parsing = false;
if ((str[i] == ',') || (str[i] == ']'))
terminate_parsing = true; // hit the end of a value, parse it in the next block
else
{
// keep grabbing chars to build up the value
tmp += str[i];
if (i == str.length() - 1)
terminate_parsing = true; // end of string, finish parsing
}
if (terminate_parsing)
{
Value v = DeserializeValue(tmp, &had_error, depth_stack);
if (had_error)
return Value();
if (v.GetType() != NULLVal)
a.push_back(v);
str = str.substr(i + 1, str.length());
break;
}
}
}
return a;
}
static Value DeserializeObj(const std::string& _str, std::stack<StackDepthType>& depth_stack)
{
Object obj;
std::string str = Trim(_str);
if ((str[0] != '{') && (str[str.length() - 1] != '}'))
return Value();
else
str = str.substr(1, str.length() - 2);
while (str.length() > 0)
{
// Get the key name
size_t start_quote_idx = GetQuotePos(str);
size_t end_quote_idx = GetQuotePos(str, start_quote_idx + 1);
size_t colon_idx = str.find(':', end_quote_idx);
if ((start_quote_idx == std::string::npos) || (end_quote_idx == std::string::npos) || (colon_idx == std::string::npos))
return Value(); // can't find key name
std::string key = str.substr(start_quote_idx + 1, end_quote_idx - start_quote_idx - 1);
if (key.length() == 0)
return Value();
bool had_error = false;
str = str.substr(colon_idx + 1, str.length());
obj[key] = DeserializeValue(str, &had_error, depth_stack);
if (had_error)
return Value();
}
return obj;
}
Value json::Deserialize(const std::string &str)
{
std::stack<StackDepthType> depth_stack;
return DeserializeInternal(str, depth_stack);
}

528
libresapi/src/api/json.h Normal file
View File

@ -0,0 +1,528 @@
/*
SuperEasyJSON
http://www.sourceforge.net/p/supereasyjson
The MIT License (MIT)
Copyright (c) 2013 Jeff Weinstein (jeff.weinstein at gmail)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
CHANGELOG:
==========
2/8/2014:
---------
MAJOR BUG FIXES, all courtesy of Per Rovegård, Ph.D.
* Feature request: HasKey and HasKeys added to Value for convenience and
to avoid having to make a temporary object.
* Strings should now be properly unescaped. Previously, as an example, the
string "\/Date(1390431949211+0100)\/\" would be parsed as
\/Date(1390431949211+0100)\/. The string is now properly parsed as
/Date(1390431949211+0100)/.
As per http://www.json.org the other escape characters including
\u+4 hex digits will now be properly unescaped. So for example,
\u0061 now becomes "A".
* Serialize now supports serializing a toplevel array (which is valid JSON).
The parameter it takes is now a Value, but existing code doesn't
need to be changed.
* Fixed bug with checking for proper opening/closing sequence for braces/brackets.
Previously, this code:
const char *json = "{\"arr\":[{}}]}";
auto val = json::Deserialize(json);
worked fine with no errors. That's a bug. I did a major overhaul so that
now improperly formatted pairs will now correctly result in an error.
* Made internal deserialize methods static
1/30/2014:
----------
* Changed #pragma once to the standard #ifndef header guard style for
better compatibility.
* Added a [] operator for Value that takes a const char* as an argument
to avoid having to explicitly (and annoyingly) cast to std::string.
Thus, my_value["asdf"] = "a string" should now work fine.
The same has been added to the Object class.
* Added non-operator methods of casting a Value to int/string/bool/etc.
Implicitly casting a Value to a std::string doesn't work as per C++
rules. As such, previously to assign a Value to a std::string you
had to do:
my_std_string = (std::string)my_value;
You can now instead do:
my_std_string = my_value.ToString();
If you want more information on why this can't be done, please read
this topic for more details:
http://stackoverflow.com/questions/3518145/c-overloading-conversion-operator-for-custom-type-to-stdstring
1/27/2014
----------
* Deserialize will now return a NULLType Value instance if there was an
error instead of asserting. This way you can handle however you want to
invalid JSON being passed in. As a top level object must be either an
array or an object, a NULL value return indicates an invalid result.
1/11/2014
---------
* Major bug fix: Strings containing []{} characters could cause
parsing errors under certain conditions. I've just tested
the class parsing a 300KB JSON file with all manner of bizarre
characters and permutations and it worked, so hopefully this should
be the end of "major bug" fixes.
1/10/2014
---------
Bug fixes courtesy of Gerry Beauregard:
* Pretty big bug: was using wrong string paramter in ::Deserialize
and furthermore it wasn't being trimmed.
* Object::HasKeys now casts the return value to avoid compiler warnings.
* Slight optimization to the Trim function
* Made asserts in ::Deserialize easier to read
1/9/2014
--------
* Major bug fix: for JSON strings containing \" (as in, two characters,
not the escaped " character), the lib would mess up and not parse
correctly.
* Major bug fix: I erroneously was assuming that all root JSON types
had to be an object. This was an oversight, as a root JSON
object can be an array. I have therefore changed the Deserialize
method to return a json::Value rather than a json::Object. This
will NOT impact any existing code you have, as a json::Value will
cast to a json::Object (if it is indeed an object). But for
correctness, you should be using json::Value = Deserialize...
The Value type can be checked if it's an array (or any other type),
and furthermore can even be accessed with the [] operator for
convenience.
* I've made the NULL value type set numeric fields to 0 and bool to false.
This is for convenience for using the NULL type as a default return
value in your code.
* asserts added to casting (Gerry Beauregard)
* Added method HasKeys to json::Object which will check if all the keys
specified are in the object, returning the index of the first key
not found or -1 if all found (hoppe).
1/4/2014
--------
* Fixed bug where booleans were being parsed as doubles (Gerry Beauregard).
1/2/2014 v3
------------
* More missing headers added for VisualStudio 2012
* Switched to snprintf instead of sprintf (or sprintf_s in MSVC)
1/2/2014 v2
-----------
* Added yet more missing headers for compiling on GNU and Linux systems
* Made Deserialize copy the passed in string so it won't mangle it
1/2/2014
--------
* Fixed previous changelog years. Got ahead of myself and marked them
as 2014 when they were in fact done in 2013.
* Added const version of [] to Array/Object/Value
* Removed C++11 requirements, should work with older compilers
(thanks to Meng Wang for pointing that out)
* Made ValueMap and ValueVector typedefs in Object/Value public
so you can actually iterate over the class
* Added HasKey and HasValue to Object/Array for convenience
(note this could have been done comparing .find to .end)
12/29/2013 v2
-------------
* Added .size() field to Value. Returns 1 for non Array/Object types,
otherwise the number of elements contained.
* Added .find() to Object to search for a key. Returns Object::end()
if not found, otherwise the Value.
Example: bool found = my_obj.find("some key") != my_obj.end();
* Added .find() to Array to search for a value. Just a convenience
wrapper for std::find(Array::begin(), Array::end(), Value)
* Added ==, !=, <, >, <=, >= operators to Object/Array/Value.
For Objects/Arrays, the operators function just like they do for a
std::map and std::vector, respectively.
* Added IsNumeric to Value to indicate if it's an int/float/double type.
12/29/2013
----------
* Added the DoubleVal type which stores, you guessed it, double values.
* Bug fix for floats with an exact integer value. Now, setting any numerical
field will also set the fields for the other numerical types. So if you
have obj["value"] = 12, then the int/float/double cast methods will
return 12/12.0f/12.0. Previously, in the example above, only the int
value was set, making a cast to float return 0.
* Bug fix for deserializing JSON strings that contained large integer values.
Now if the numerical value of a key in a JSON string contains a number
less than INT_MIN or greater than INT_MAX it will be stored as a double.
Note that as mentioned above, all numerical fields are set.
* Should work fine with scientific notation values now.
12/28/2013
----------
* Fixed a bug where if there were spaces around values or key names in a JSON
string passed in to Deserialize, invalid results or asserts would occur.
(Fix courtesy of Gerry Beauregard)
* Added method named "Clear()" to Object/Array/Value to reset state
* Added license to header file for easyness (totally valid word).
*/
#ifndef __SUPER_EASY_JSON_H__
#define __SUPER_EASY_JSON_H__
#include <vector>
#include <map>
#include <string>
#include <assert.h>
namespace json
{
enum ValueType
{
NULLVal,
StringVal,
IntVal,
FloatVal,
DoubleVal,
ObjectVal,
ArrayVal,
BoolVal
};
class Value;
class Object
{
public:
typedef std::map<std::string, Value> ValueMap;
protected:
ValueMap mValues;
public:
Object();
Object(const Object& obj);
Object& operator =(const Object& obj);
friend bool operator ==(const Object& lhs, const Object& rhs);
inline friend bool operator !=(const Object& lhs, const Object& rhs) {return !(lhs == rhs);}
friend bool operator <(const Object& lhs, const Object& rhs);
inline friend bool operator >(const Object& lhs, const Object& rhs) {return operator<(rhs, lhs);}
inline friend bool operator <=(const Object& lhs, const Object& rhs) {return !operator>(lhs, rhs);}
inline friend bool operator >=(const Object& lhs, const Object& rhs) {return !operator<(lhs, rhs);}
Value& operator [](const std::string& key);
const Value& operator [](const std::string& key) const;
Value& operator [](const char* key);
const Value& operator [](const char* key) const;
ValueMap::const_iterator begin() const;
ValueMap::const_iterator end() const;
ValueMap::iterator begin();
ValueMap::iterator end();
// Find will return end() if the key can't be found, just like std::map does.
ValueMap::iterator find(const std::string& key);
ValueMap::const_iterator find(const std::string& key) const;
// Convenience wrapper to find to search for a key
bool HasKey(const std::string& key) const;
// Checks if the object contains all the keys in the array. If it does, returns -1.
// If it doesn't, returns the index of the first key it couldn't find.
int HasKeys(const std::vector<std::string>& keys) const;
int HasKeys(const char* keys[], int key_count) const;
// Removes all values and resets the state back to default
void Clear();
size_t size() const {return mValues.size();}
};
class Array
{
public:
typedef std::vector<Value> ValueVector;
protected:
ValueVector mValues;
public:
Array();
Array(const Array& a);
Array& operator =(const Array& a);
friend bool operator ==(const Array& lhs, const Array& rhs);
inline friend bool operator !=(const Array& lhs, const Array& rhs) {return !(lhs == rhs);}
friend bool operator <(const Array& lhs, const Array& rhs);
inline friend bool operator >(const Array& lhs, const Array& rhs) {return operator<(rhs, lhs);}
inline friend bool operator <=(const Array& lhs, const Array& rhs) {return !operator>(lhs, rhs);}
inline friend bool operator >=(const Array& lhs, const Array& rhs) {return !operator<(lhs, rhs);}
Value& operator[] (size_t i);
const Value& operator[] (size_t i) const;
ValueVector::const_iterator begin() const;
ValueVector::const_iterator end() const;
ValueVector::iterator begin();
ValueVector::iterator end();
// Just a convenience wrapper for doing a std::find(Array::begin(), Array::end(), Value)
ValueVector::iterator find(const Value& v);
ValueVector::const_iterator find(const Value& v) const;
// Convenience wrapper to check if a value is in the array
bool HasValue(const Value& v) const;
// Removes all values and resets the state back to default
void Clear();
void push_back(const Value& v);
void insert(size_t index, const Value& v);
size_t size() const;
};
class Value
{
protected:
ValueType mValueType;
int mIntVal;
float mFloatVal;
double mDoubleVal;
std::string mStringVal;
Object mObjectVal;
Array mArrayVal;
bool mBoolVal;
public:
Value() : mValueType(NULLVal), mIntVal(0), mFloatVal(0), mDoubleVal(0), mBoolVal(false) {}
Value(int v) : mValueType(IntVal), mIntVal(v), mFloatVal((float)v), mDoubleVal((double)v) {}
Value(float v) : mValueType(FloatVal), mFloatVal(v), mIntVal((int)v), mDoubleVal((double)v) {}
Value(double v) : mValueType(DoubleVal), mDoubleVal(v), mIntVal((int)v), mFloatVal((float)v) {}
Value(const std::string& v) : mValueType(StringVal), mStringVal(v) {}
Value(const char* v) : mValueType(StringVal), mStringVal(v) {}
Value(const Object& v) : mValueType(ObjectVal), mObjectVal(v) {}
Value(const Array& v) : mValueType(ArrayVal), mArrayVal(v) {}
Value(const bool v) : mValueType(BoolVal), mBoolVal(v) {}
Value(const Value& v);
ValueType GetType() const {return mValueType;}
Value& operator =(const Value& v);
friend bool operator ==(const Value& lhs, const Value& rhs);
inline friend bool operator !=(const Value& lhs, const Value& rhs) {return !(lhs == rhs);}
friend bool operator <(const Value& lhs, const Value& rhs);
inline friend bool operator >(const Value& lhs, const Value& rhs) {return operator<(rhs, lhs);}
inline friend bool operator <=(const Value& lhs, const Value& rhs) {return !operator>(lhs, rhs);}
inline friend bool operator >=(const Value& lhs, const Value& rhs) {return !operator<(lhs, rhs);}
// For use with Array/ObjectVal types, respectively
Value& operator [](size_t idx);
const Value& operator [](size_t idx) const;
Value& operator [](const std::string& key);
const Value& operator [](const std::string& key) const;
Value& operator [](const char* key);
const Value& operator [](const char* key) const;
bool HasKey(const std::string& key) const;
int HasKeys(const std::vector<std::string>& keys) const;
int HasKeys(const char* keys[], int key_count) const;
// non-operator versions
int ToInt() const {assert(IsNumeric()); return mIntVal;}
float ToFloat() const {assert(IsNumeric()); return mFloatVal;}
double ToDouble() const {assert(IsNumeric()); return mDoubleVal;}
bool ToBool() const {assert(mValueType == BoolVal); return mBoolVal;}
std::string ToString() const {assert(mValueType == StringVal); return mStringVal;}
Object ToObject() const {assert(mValueType == ObjectVal); return mObjectVal;}
Array ToArray() const {assert(mValueType == ArrayVal); return mArrayVal;}
// Please note that as per C++ rules, implicitly casting a Value to a std::string won't work.
// This is because it could use the int/float/double/bool operators as well. So to assign a
// Value to a std::string you can either do:
// my_string = (std::string)my_value
// Or you can now do:
// my_string = my_value.ToString();
//
operator int() const {assert(IsNumeric()); return mIntVal;}
operator float() const {assert(IsNumeric()); return mFloatVal;}
operator double() const {assert(IsNumeric()); return mDoubleVal;}
operator bool() const {assert(mValueType == BoolVal); return mBoolVal;}
operator std::string() const {assert(mValueType == StringVal); return mStringVal;}
operator Object() const {assert(mValueType == ObjectVal); return mObjectVal;}
operator Array() const {assert(mValueType == ArrayVal); return mArrayVal;}
bool IsNumeric() const {return (mValueType == IntVal) || (mValueType == DoubleVal) || (mValueType == FloatVal);}
// Returns 1 for anything not an Array/ObjectVal
size_t size() const;
// Resets the state back to default, aka NULLVal
void Clear();
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Converts a JSON Object or Array instance into a JSON string representing it.
std::string Serialize(const Value& obj);
// If there is an error, Value will be NULLType
Value Deserialize(const std::string& str);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
inline bool operator ==(const Object& lhs, const Object& rhs)
{
return lhs.mValues == rhs.mValues;
}
inline bool operator <(const Object& lhs, const Object& rhs)
{
return lhs.mValues < rhs.mValues;
}
inline bool operator ==(const Array& lhs, const Array& rhs)
{
return lhs.mValues == rhs.mValues;
}
inline bool operator <(const Array& lhs, const Array& rhs)
{
return lhs.mValues < rhs.mValues;
}
/* When comparing different numeric types, this method works the same as if you compared different numeric types
on your own. Thus it performs the same as if you, for example, did this:
int a = 1;
float b = 1.1f;
bool equivalent = a == b;
The same logic applies to the other comparison operators.
*/
inline bool operator ==(const Value& lhs, const Value& rhs)
{
if ((lhs.mValueType != rhs.mValueType) && !lhs.IsNumeric() && !rhs.IsNumeric())
return false;
switch (lhs.mValueType)
{
case StringVal : return lhs.mStringVal == rhs.mStringVal;
case IntVal : if (rhs.GetType() == FloatVal)
return lhs.mIntVal == rhs.mFloatVal;
else if (rhs.GetType() == DoubleVal)
return lhs.mIntVal == rhs.mDoubleVal;
else if (rhs.GetType() == IntVal)
return lhs.mIntVal == rhs.mIntVal;
else
return false;
case FloatVal : if (rhs.GetType() == FloatVal)
return lhs.mFloatVal == rhs.mFloatVal;
else if (rhs.GetType() == DoubleVal)
return lhs.mFloatVal == rhs.mDoubleVal;
else if (rhs.GetType() == IntVal)
return lhs.mFloatVal == rhs.mIntVal;
else
return false;
case DoubleVal : if (rhs.GetType() == FloatVal)
return lhs.mDoubleVal == rhs.mFloatVal;
else if (rhs.GetType() == DoubleVal)
return lhs.mDoubleVal == rhs.mDoubleVal;
else if (rhs.GetType() == IntVal)
return lhs.mDoubleVal == rhs.mIntVal;
else
return false;
case BoolVal : return lhs.mBoolVal == rhs.mBoolVal;
case ObjectVal : return lhs.mObjectVal == rhs.mObjectVal;
case ArrayVal : return lhs.mArrayVal == rhs.mArrayVal;
default:
return true;
}
}
inline bool operator <(const Value& lhs, const Value& rhs)
{
if ((lhs.mValueType != rhs.mValueType) && !lhs.IsNumeric() && !rhs.IsNumeric())
return false;
switch (lhs.mValueType)
{
case StringVal : return lhs.mStringVal < rhs.mStringVal;
case IntVal : if (rhs.GetType() == FloatVal)
return lhs.mIntVal < rhs.mFloatVal;
else if (rhs.GetType() == DoubleVal)
return lhs.mIntVal < rhs.mDoubleVal;
else if (rhs.GetType() == IntVal)
return lhs.mIntVal < rhs.mIntVal;
else
return false;
case FloatVal : if (rhs.GetType() == FloatVal)
return lhs.mFloatVal < rhs.mFloatVal;
else if (rhs.GetType() == DoubleVal)
return lhs.mFloatVal < rhs.mDoubleVal;
else if (rhs.GetType() == IntVal)
return lhs.mFloatVal < rhs.mIntVal;
else
return false;
case DoubleVal : if (rhs.GetType() == FloatVal)
return lhs.mDoubleVal < rhs.mFloatVal;
else if (rhs.GetType() == DoubleVal)
return lhs.mDoubleVal < rhs.mDoubleVal;
else if (rhs.GetType() == IntVal)
return lhs.mDoubleVal < rhs.mIntVal;
else
return false;
case BoolVal : return lhs.mBoolVal < rhs.mBoolVal;
case ObjectVal : return lhs.mObjectVal < rhs.mObjectVal;
case ArrayVal : return lhs.mArrayVal < rhs.mArrayVal;
default:
return true;
}
}
}
#endif //__SUPER_EASY_JSON_H__

View File

@ -0,0 +1,57 @@
TEMPLATE = lib
CONFIG += staticlib
CONFIG -= qt
TARGET = resapi
DESTDIR = lib
CONFIG += libmicrohttpd
INCLUDEPATH += ../../libretroshare/src
win32{
DEFINES *= WINDOWS_SYS
INCLUDEPATH += $$PWD/../../../libs/include
}
libmicrohttpd{
SOURCES += \
api/ApiServerMHD.cpp
HEADERS += \
api/ApiServerMHD.h
}
SOURCES += \
api/ApiServer.cpp \
api/json.cpp \
api/JsonStream.cpp \
api/ResourceRouter.cpp \
api/PeersHandler.cpp \
api/Operators.cpp \
api/IdentityHandler.cpp \
api/ServiceControlHandler.cpp \
api/StateTokenServer.cpp \
api/GxsResponseTask.cpp \
api/FileSearchHandler.cpp \
api/TransfersHandler.cpp \
api/RsControlModule.cpp \
api/GetPluginInterfaces.cpp
HEADERS += \
api/ApiServer.h \
api/json.h \
api/JsonStream.h \
api/ApiTypes.h \
api/ResourceRouter.h \
api/PeersHandler.h \
api/Operators.h \
api/IdentityHandler.h \
api/ServiceControlHandler.h \
api/GxsMetaOperators.h \
api/StateTokenServer.h \
api/GxsResponseTask.h \
api/Pagination.h \
api/FileSearchHandler.h \
api/TransfersHandler.h \
api/RsControlModule.h \
api/GetPluginInterfaces.h

View File

@ -3,6 +3,8 @@ TARGET = retroshare-nogui
CONFIG += bitdht
#CONFIG += introserver
#CONFIG += sshserver
# webinterface, requires libmicrohttpd
CONFIG += webui
CONFIG -= qt xml gui
# if you are linking against the libretroshare with gxs.
@ -197,6 +199,11 @@ introserver {
DEFINES *= RS_INTRO_SERVER
}
webui {
DEFINES *= ENABLE_WEBUI
LIBS += ../../libresapi/src/lib/libresapi.a -lmicrohttpd
INCLUDEPATH += ../../libresapi/src
}
sshserver {

View File

@ -51,10 +51,12 @@
// NASTY GLOBAL VARIABLE HACK - NEED TO THINK OF A BETTER SYSTEM.
#include "rpc/proto/rpcprotosystem.h"
void generatePasswordHash() ;
#endif
#ifdef RS_SSH_SERVER
void generatePasswordHash() ;
#ifdef ENABLE_WEBUI
#include "api/ApiServerMHD.h"
#include "api/RsControlModule.h"
#endif
/* Basic instructions for running libretroshare as background thread.
@ -69,6 +71,33 @@ void generatePasswordHash() ;
int main(int argc, char **argv)
{
#ifdef ENABLE_WEBUI
std::string docroot;
bool is_portable = false;
#ifdef WINDOWS_SYS
// test for portable version
if (GetFileAttributes(L"portable") != (DWORD) -1) {
is_portable = true;
}
#endif
resource_api::ApiServerMHD httpd(docroot, 9090);
resource_api::RsControlModule ctrl_mod(argc, argv, httpd.getApiServer().getStateTokenServer(), &httpd.getApiServer());
httpd.getApiServer().addResourceHandler("control", dynamic_cast<resource_api::ResourceRouter*>(&ctrl_mod), &resource_api::RsControlModule::handleRequest);
httpd.start();
while(ctrl_mod.processShouldExit() == false)
{
usleep(20*1000);
}
httpd.stop();
return 1;
#endif
/* Retroshare startup is configured using an RsInit object.
* This is an opaque class, which the user cannot directly tweak
* If you want to peek at whats happening underneath look in