- added profile import/creation
- fixed leaking file descriptors
- added upload handler for small files
- fixed terminal thread
- removed some unused parameter warnings

git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@8485 b45a01b8-16f6-495d-af2f-9b41ad6348cc
This commit is contained in:
electron128 2015-06-16 12:35:07 +00:00
parent 3ed1be74a6
commit 8fc3917c4b
21 changed files with 1343 additions and 121 deletions

View File

@ -256,6 +256,7 @@ ApiServer::ApiServer():
mMtx("ApiServer mMtx"),
mStateTokenServer(),
mLivereloadhandler(&mStateTokenServer),
mTmpBlobStore(&mStateTokenServer),
mMainModules(0)
{
mRouter.addResourceHandler("statetokenservice", dynamic_cast<ResourceRouter*>(&mStateTokenServer),

View File

@ -10,6 +10,7 @@
#include "FileSearchHandler.h"
#include "TransfersHandler.h"
#include "LivereloadHandler.h"
#include "TmpBlobStore.h"
namespace resource_api{
@ -73,12 +74,14 @@ public:
void addResourceHandler(std::string name, T* instance, void (T::*callback)(Request& req, Response& resp));
StateTokenServer* getStateTokenServer(){ return &mStateTokenServer; }
TmpBlobStore* getTmpBlobStore(){ return &mTmpBlobStore; }
private:
RsMutex mMtx;
StateTokenServer mStateTokenServer; // goes first, as others may depend on it
// is always loaded, because it has no dependencies
LivereloadHandler mLivereloadhandler;
TmpBlobStore mTmpBlobStore;
// only pointers here, to load/unload modules at runtime
ApiServerMainModules* mMainModules; // loaded when RS is started

View File

@ -2,6 +2,7 @@
#include <iostream>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdio>
#include <algorithm>
@ -44,9 +45,15 @@ struct MHD_Response * MHD_create_response_from_fd(size_t size, int fd)
{
unsigned char *buf = (unsigned char *)malloc(size) ;
if(buf == 0 || read(fd,buf,size) != size)
if(buf == 0)
{
std::cerr << "replacement MHD_create_response_from_fd: malloc failed, size was " << size << std::endl;
close(fd);
return NULL ;
}
if(read(fd,buf,size) != size)
{
std::cerr << "malloc failed or READ error in file descriptor " << fd << " requested read size was " << size << std::endl;
std::cerr << "replacement MHD_create_response_from_fd: READ error in file descriptor " << fd << " requested read size was " << size << std::endl;
close(fd);
free(buf) ;
return NULL ;
@ -69,6 +76,7 @@ std::string getDefaultDocroot()
const char* API_ENTRY_PATH = "/api/v2";
const char* FILESTREAMER_ENTRY_PATH = "/fstream/";
const char* STATIC_FILES_ENTRY_PATH = "/static/";
const char* UPLOAD_ENTRY_PATH = "/upload/";
static void secure_queue_response(MHD_Connection *connection, unsigned int status_code, struct MHD_Response* response);
static void sendMessage(MHD_Connection *connection, unsigned int status, std::string message);
@ -86,6 +94,65 @@ public:
const char *upload_data, size_t *upload_data_size) = 0;
};
// handles calls to the resource_api
class MHDUploadHandler: public MHDHandlerBase
{
public:
MHDUploadHandler(ApiServer* s): mState(BEGIN), mApiServer(s){}
virtual ~MHDUploadHandler(){}
// 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 nothing 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;
}
}
std::vector<uint8_t> bytes(mRequesString.begin(), mRequesString.end());
int id = mApiServer->getTmpBlobStore()->storeBlob(bytes);
resource_api::JsonStream responseStream;
if(id)
responseStream << makeKeyValue("ok", true);
else
responseStream << makeKeyValue("ok", false);
responseStream << makeKeyValueReference("id", id);
std::string result = responseStream.getJsonString();
struct MHD_Response* resp = MHD_create_response_from_data(result.size(), (void*)result.data(), 0, 1);
MHD_add_response_header(resp, "Content-Type", "application/json");
secure_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;
};
// handles calls to the resource_api
class MHDApiHandler: public MHDHandlerBase
{
@ -94,7 +161,7 @@ public:
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 *url, const char *method, const char */*version*/,
const char *upload_data, size_t *upload_data_size)
{
// new request
@ -198,8 +265,8 @@ public:
// 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)
const char *url, const char */*method*/, const char */*version*/,
const char */*upload_data*/, size_t */*upload_data_size*/)
{
if(rsFiles == 0)
{
@ -346,7 +413,7 @@ ApiServerMHD::~ApiServerMHD()
stop();
}
bool ApiServerMHD::configure(std::string docroot, uint16_t port, std::string bind_address, bool allow_from_all)
bool ApiServerMHD::configure(std::string docroot, uint16_t port, std::string /*bind_address*/, bool allow_from_all)
{
mRootDir = docroot;
// make sure the docroot dir ends with a slash
@ -512,10 +579,14 @@ int ApiServerMHD::accessHandlerCallback(MHD_Connection *connection,
url = url + strlen(STATIC_FILES_ENTRY_PATH);
// else server static files
std::string filename = mRootDir + url;
// important: binary open mode,
// important: binary open mode (windows)
// 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)
#ifdef O_BINARY
int fd = open(filename.c_str(), O_RDONLY | O_BINARY);
#else
int fd = open(filename.c_str(), O_RDONLY);
#endif
if(fd == -1)
{
#warning sending untrusted string to the browser
std::string msg = "<html><body><p>Error: can't open the requested file. Path is &quot;"+filename+"&quot;</p></body></html>";
@ -524,9 +595,9 @@ int ApiServerMHD::accessHandlerCallback(MHD_Connection *connection,
}
struct stat s;
if(fstat(fileno(fd), &s) == -1)
if(fstat(fd, &s) == -1)
{
fclose(fd);
close(fd);
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");
@ -561,14 +632,21 @@ int ApiServerMHD::accessHandlerCallback(MHD_Connection *connection,
else
type = "application/octet-stream";
struct MHD_Response* resp = MHD_create_response_from_fd(s.st_size, fileno(fd));
struct MHD_Response* resp = MHD_create_response_from_fd(s.st_size, fd);
MHD_add_response_header(resp, "Content-Type", type);
secure_queue_response(connection, MHD_HTTP_OK, resp);
MHD_destroy_response(resp);
fclose(fd);
return MHD_YES;
}
if(strstr(url, UPLOAD_ENTRY_PATH) == url)
{
// create a new handler and store it in con_cls
MHDHandlerBase* handler = new MHDUploadHandler(mApiServer);
*con_cls = (void*) handler;
return handler->handleRequest(connection, url, method, version, upload_data, upload_data_size);
}
// if url is not a valid path, then serve a help page
sendMessage(connection, MHD_HTTP_NOT_FOUND,
"This address is invalid. Try one of the adresses below:<br/>"
@ -582,8 +660,8 @@ int ApiServerMHD::accessHandlerCallback(MHD_Connection *connection,
return MHD_YES;
}
void ApiServerMHD::requestCompletedCallback(struct MHD_Connection *connection,
void **con_cls, MHD_RequestTerminationCode toe)
void ApiServerMHD::requestCompletedCallback(struct MHD_Connection */*connection*/,
void **con_cls, MHD_RequestTerminationCode /*toe*/)
{
if(*con_cls)
{

View File

@ -39,6 +39,8 @@ RsControlModule::RsControlModule(int argc, char **argv, StateTokenServer* sts, A
addResourceHandler("password", this, &RsControlModule::handlePassword);
addResourceHandler("login", this, &RsControlModule::handleLogin);
addResourceHandler("shutdown", this, &RsControlModule::handleShutdown);
addResourceHandler("import_pgp", this, &RsControlModule::handleImportPgp);
addResourceHandler("create_location", this, &RsControlModule::handleCreateLocation);
}
RsControlModule::~RsControlModule()
@ -57,6 +59,12 @@ bool RsControlModule::askForPassword(const std::string &key_details, bool prev_i
cancelled = false ;
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
if(mFixedPassword != "")
{
password = mFixedPassword;
return true;
}
mWantPassword = true;
mKeyName = key_details;
mPassword = "";
@ -189,7 +197,7 @@ void RsControlModule::run()
std::cerr << "RsControlModule: Retroshare stopped. Bye!" << std::endl;
}
void RsControlModule::handleRunState(Request &req, Response &resp)
void RsControlModule::handleRunState(Request &, Response &resp)
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
std::string state;
@ -221,7 +229,7 @@ void RsControlModule::handleRunState(Request &req, Response &resp)
resp.setOk();
}
void RsControlModule::handleIdentities(Request &req, Response &resp)
void RsControlModule::handleIdentities(Request &, Response &resp)
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
if(mRunState == WAITING_INIT || mRunState == FATAL_ERROR)
@ -247,7 +255,7 @@ void RsControlModule::handleIdentities(Request &req, Response &resp)
resp.setOk();
}
void RsControlModule::handleLocations(Request &req, Response &resp)
void RsControlModule::handleLocations(Request &, Response &resp)
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
if(mRunState == WAITING_INIT || mRunState == FATAL_ERROR)
@ -313,13 +321,139 @@ void RsControlModule::handleLogin(Request &req, Response &resp)
resp.setOk();
}
void RsControlModule::handleShutdown(Request &req, Response &resp)
void RsControlModule::handleShutdown(Request &, Response &resp)
{
RsStackMutex stack(mExitFlagMtx); // ********** LOCKED **********
mProcessShouldExit = true;
resp.setOk();
}
void RsControlModule::handleImportPgp(Request &req, Response &resp)
{
std::string key_string;
req.mStream << makeKeyValueReference("key_string", key_string);
if(key_string.empty())
{
resp.setFail("required field key_string is empty");
return;
}
RsPgpId pgp_id;
std::string error_string;
if(RsAccounts::ImportIdentityFromString(key_string, pgp_id, error_string))
{
resp.mDataStream << makeKeyValueReference("pgp_id", pgp_id);
resp.setOk();
return;
}
resp.setFail("Failed to import key: " + error_string);
}
void RsControlModule::handleCreateLocation(Request &req, Response &resp)
{
std::string hidden_address;
std::string hidden_port_str;
req.mStream << makeKeyValueReference("hidden_adress", hidden_address)
<< makeKeyValueReference("hidden_port", hidden_port_str);
uint16_t hidden_port = 0;
if(hidden_address.empty() != hidden_port_str.empty())
{
resp.setFail("you must both specify string hidden_adress and string hidden_port to create a hidden node.");
return;
}
if(!hidden_address.empty())
{
int p;
if(sscanf(hidden_port_str.c_str(), "%i", &p) != 1)
{
resp.setFail("failed to parse hidden_port, not a number. Must be a dec or hex number.");
return;
}
if(p < 0 || p > 0xFFFF)
{
resp.setFail("hidden_port out of range. It must fit into uint16!");
return;
}
hidden_port = p;
}
RsPgpId pgp_id;
std::string pgp_name;
std::string pgp_password;
std::string ssl_name;
req.mStream << makeKeyValueReference("pgp_id", pgp_id)
<< makeKeyValueReference("pgp_name", pgp_name)
<< makeKeyValueReference("pgp_password", pgp_password)
<< makeKeyValueReference("ssl_name", ssl_name);
if(pgp_password.empty())
{
resp.setFail("Error: pgp_password is empty.");
return;
}
// pgp_id is set: use existing pgp key
// pgp_name is set: attempt to create a new key
if(pgp_id.isNull() && (pgp_name.empty()||pgp_password.empty()))
{
resp.setFail("You have to set pgp_id to use an existing key, or pgp_name and pgp_password to create a new pgp key.");
return;
}
if(pgp_id.isNull())
{
std::string err_string;
if(!RsAccounts::GeneratePGPCertificate(pgp_name, "", pgp_password, pgp_id, 2048, err_string))
{
resp.setFail("could not cerate pgp key: "+err_string);
return;
}
}
if(hidden_port)
RsInit::SetHiddenLocation(hidden_address, hidden_port);
std::string ssl_password = RSRandom::random_alphaNumericString(RsInit::getSslPwdLen()) ;
/* GenerateSSLCertificate - selects the PGP Account */
//RsInit::SelectGPGAccount(PGPId);
RsPeerId ssl_id;
std::string err_string;
// give the password to the password callback
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
mFixedPassword = pgp_password;
}
bool ssl_ok = RsAccounts::GenerateSSLCertificate(pgp_id, "", ssl_name, "", hidden_port!=0, ssl_password, ssl_id, err_string);
// clear fixed password to restore normal password operation
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
mFixedPassword = "";
}
if (ssl_ok)
{
// load ssl password and load account
RsInit::LoadPassword(ssl_password);
// trigger login in init thread
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********
mLoadPeerId = ssl_id;
}
resp.mDataStream << makeKeyValueReference("pgp_id", pgp_id)
<< makeKeyValueReference("pgp_name", pgp_name)
<< makeKeyValueReference("ssl_name", ssl_name)
<< makeKeyValueReference("ssl_id", ssl_id);
resp.setOk();
return;
}
resp.setFail("could not create a new location. Error: "+err_string);
}
void RsControlModule::setRunState(RunState s, std::string errstr)
{
RsStackMutex stack(mDataMtx); // ********** LOCKED **********

View File

@ -10,7 +10,7 @@ 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
// - this module handles everything, no 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
@ -47,6 +47,8 @@ private:
void handlePassword(Request& req, Response& resp);
void handleLogin(Request& req, Response& resp);
void handleShutdown(Request& req, Response& resp);
void handleImportPgp(Request& req, Response& resp);
void handleCreateLocation(Request& req, Response& resp);
void setRunState(RunState s, std::string errstr = "");
// for startup
@ -76,6 +78,11 @@ private:
bool mWantPassword;
std::string mKeyName;
std::string mPassword;
// for ssl cert generation:
// we know the password already, so we want to avoid to rpompt the user
// we store the password in this variable, it has higher priority than the normal password variable
// it is also to avoid a lock, when we make a synchronous call into librs, like in ssl cert generation
std::string mFixedPassword;
};
} // namespace resource_api

View File

@ -0,0 +1,120 @@
#include "TmpBlobStore.h"
#include <util/rsrandom.h>
#include <time.h>
namespace resource_api{
TmpBlobStore::TmpBlobStore(StateTokenServer* sts):
mStateTokenServer(sts), mMtx("TmpBlobStore::mMtx"), mBlobs(0)
{
mStateTokenServer->registerTickClient(this);
}
TmpBlobStore::~TmpBlobStore()
{
mStateTokenServer->unregisterTickClient(this);
RS_STACK_MUTEX(mMtx);
Blob* blob = mBlobs;
while(blob)
{
Blob* next = blob->next;
delete blob;
blob = next;
}
}
void TmpBlobStore::tick()
{
RS_STACK_MUTEX(mMtx);
Blob* prev = 0;
Blob* current = mBlobs;
time_t now = time(NULL);
for(; current != 0; current = current->next)
{
if(current->timestamp + BLOB_STORE_TIME_SECS < now)
{
if(prev)
prev->next = current->next;
else
mBlobs = current->next;
delete current;
return;
}
prev = current;
}
}
uint32_t TmpBlobStore::storeBlob(std::vector<uint8_t>& bytes)
{
if(bytes.size() > MAX_BLOBSIZE)
return 0;
RS_STACK_MUTEX(mMtx);
Blob* blob = new Blob();
blob->bytes.swap(bytes);
blob->timestamp = time(NULL);
blob->id = locked_make_id();
blob->next = 0;
if(!mBlobs)
{
mBlobs = blob;
return blob->id;
}
Blob* blob2 = mBlobs;
while(blob2->next)
blob2 = blob2->next;
blob2->next = blob;
return blob->id;
}
bool TmpBlobStore::fetchBlob(uint32_t blobid, std::vector<uint8_t>& bytes)
{
RS_STACK_MUTEX(mMtx);
Blob* prev = 0;
Blob* current = mBlobs;
for(; current != 0; current = current->next)
{
if(current->id == blobid)
{
bytes.swap(current->bytes);
if(prev)
prev->next = current->next;
else
mBlobs = current->next;
delete current;
return true;
}
prev = current;
}
return false;
}
TmpBlobStore::Blob* TmpBlobStore::locked_findblob(uint32_t id)
{
Blob* blob;
for(blob = mBlobs; blob != 0; blob = blob->next)
{
if(blob->id == id)
return blob;
}
return 0;
}
uint32_t TmpBlobStore::locked_make_id()
{
uint32_t id = RSRandom::random_u32();
// make sure the id is not in use already
while(locked_findblob(id))
id = RSRandom::random_u32();
return id;
}
} // namespace resource_api

View File

@ -0,0 +1,54 @@
#pragma once
#include "StateTokenServer.h"
namespace resource_api{
// store for temporary binary data
// use cases: upload of avatar image, upload of certificate file
// those files are first store in this class, then and a handler will fetch the datas later
// blobs are removed if they are not used after xx hours
class TmpBlobStore: private Tickable
{
public:
TmpBlobStore(StateTokenServer* sts);
virtual ~TmpBlobStore();
// from Tickable
virtual void tick();
// 30MB should be enough for avatars pictures, pdfs and mp3s
// can remove this limit, once we can store data on disk
static const int MAX_BLOBSIZE = 30*1000*1000;
static const int BLOB_STORE_TIME_SECS = 12*60*60;
// steals the bytes
// returns a blob number as identifier
// returns null on failure
uint32_t storeBlob(std::vector<uint8_t>& bytes);
// fetch blob with given id
// the blob is removed from the store
// return true on success
bool fetchBlob(uint32_t blobid, std::vector<uint8_t>& bytes);
private:
class Blob{
public:
std::vector<uint8_t> bytes;
time_t timestamp;
uint32_t id;
Blob* next;
};
Blob* locked_findblob(uint32_t id);
uint32_t locked_make_id();
StateTokenServer* const mStateTokenServer;
RsMutex mMtx;
uint32_t mLastBlobId;
Blob* mBlobs;
};
} // namespace resource_api

View File

@ -37,7 +37,8 @@ SOURCES += \
api/RsControlModule.cpp \
api/GetPluginInterfaces.cpp \
api/ChatHandler.cpp \
api/LivereloadHandler.cpp
api/LivereloadHandler.cpp \
api/TmpBlobStore.cpp
HEADERS += \
api/ApiServer.h \
@ -58,4 +59,5 @@ HEADERS += \
api/RsControlModule.h \
api/GetPluginInterfaces.h \
api/ChatHandler.h \
api/LivereloadHandler.h
api/LivereloadHandler.h \
api/TmpBlobStore.h

View File

@ -51,9 +51,11 @@ td{
padding: 0.1em;
}
.btn2{
.btn2, .box{
border-style: solid;
border-color: lime;
/*border-color: lime;*/
border-color: limeGreen;
/*border-width: 1px;*/
border-radius: 3mm;
padding: 2mm;
font-size: 10mm;

View File

@ -4,6 +4,7 @@ RS.start();
var api_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/api/v2/";
var filestreamer_url = window.location.protocol + "//" +window.location.hostname + ":" + window.location.port + "/fstream/";
var upload_url = window.location.protocol + "//" +window.location.hostname + ":" + window.location.port + "/upload/";
// livereload
function start_live_reload()
@ -716,10 +717,11 @@ var AccountSelectWidget = React.createClass({
else
return(
<div>
<div><p>select a location to log in</p></div>
{this.state.data.map(function(location){
return <div className="btn2" key={location.id} onClick ={function(){component.selectAccount(location.id);}}>{location.name} ({location.location})</div>;
})}
{
this.state.data.map(function(location){
return <div className="btn2" key={location.id} onClick ={function(){component.selectAccount(location.id);}}>login {location.name} ({location.location})</div>;
})
}
</div>
);
},
@ -757,6 +759,307 @@ var LoginWidget = React.createClass({
},
});
var LoginWidget2 = React.createClass({
debug: function(msg){
console.log(msg);
},
getInitialState: function(){
return {display_data:[], state: "waiting", hidden_node: false, error: ""};
},
componentDidMount: function()
{
this.update();
// FOR TESTING
//this.setState({state: "create_location"});
},
// this component depends on three resources
// have to fecth all, before can display anything
data: {},
clear_data: function()
{
this.debug("clear_data()");
data =
{
have_runstate: false,
runstate: "",
have_identities: false,
identities: [],
have_locations: false,
locations: [],
pgp_id: undefined,
pgp_name: undefined,
};
},
update: function()
{
this.debug("update()");
var c = this;
c.clear_data();
function check_data()
{
if(c.data.have_runstate && c.data.have_locations && c.data.have_identities)
{
c.debug("update(): check_data(): have all data, will change to display mode");
var data = [];
var pgp_ids_with_loc = [];
function pgp_id_has_location(pgp_id)
{
for(var i in pgp_ids_with_loc)
{
if(pgp_id === pgp_ids_with_loc[i])
return true;
}
return false;
}
// collect all pgp ids with location
// collect location data
for(var i in c.data.locations)
{
if(!pgp_id_has_location(c.data.locations[i].pgp_id))
pgp_ids_with_loc.push(c.data.locations[i].pgp_id);
data.push(c.data.locations[i]);
}
// collect pgp data without location
for(var i in c.data.identities)
{
if(!pgp_id_has_location(c.data.identities[i].pgp_id))
data.push(c.data.identities[i]);
}
c.setState({
display_data: data,
state: "display",
});
}
}
RS.request({path:"control/runstate"}, function(resp){
c.debug("update(): got control/runstate");
if(resp.returncode === "ok"){
c.data.have_runstate = true;
c.data.runstate = resp.data.runstate;
check_data();
}
});
RS.request({path:"control/identities"}, function(resp){
c.debug("update(): got control/identities");
if(resp.returncode === "ok"){
c.data.have_identities = true;
c.data.identities = resp.data;
check_data();
}
});
RS.request({path:"control/locations"}, function(resp){
c.debug("update(): got control/locations");
if(resp.returncode === "ok"){
c.data.have_locations = true;
c.data.locations = resp.data;
check_data();
}
});
},
shutdown: function(){
RS.request({path: "control/shutdown"});
},
selectAccount: function(id){
this.debug("login with id="+id);
RS.request({path: "control/login", data:{id: id}});
var state = this.state;
state.mode = "wait";
this.setState(state);
},
onDrop: function(event)
{
this.debug("onDrop()");
this.debug(event.dataTransfer.files);
event.preventDefault();
var reader = new FileReader();
var widget = this;
var c = this;
reader.onload = function(evt) {
c.debug("onDrop(): file loaded");
RS.request({path:"control/import_pgp", data:{key_string:evt.target.result}}, c.importCallback);
};
reader.readAsText(event.dataTransfer.files[0]);
this.setState({state:"waiting"});
},
importCallback: function(resp)
{
this.debug("importCallback()");
console.log(resp);
if(resp.returncode === "ok")
{
this.debug("import ok");
this.setState({error: "import ok"});
}
else
{
this.setState({error: "import error"});
}
this.update();
},
selectAccount: function(id){
console.log("login with id="+id)
RS.request({path: "control/login", data:{id: id}});
this.setState({state: "waiting"});
},
setupCreateLocation: function(pgp_id, pgp_name)
{
this.data.pgp_id = pgp_id;
this.data.pgp_name = pgp_name;
this.setState({state: "create_location"});
},
submitLoc: function()
{
var req = {
path: "control/create_location",
data: {
ssl_name: "nogui-webui"
}
};
if(this.data.pgp_id !== undefined)
{
req.data["pgp_password"] = this.refs.pwd1.getDOMNode().value;
req.data["pgp_id"] = this.data.pgp_id;
}
else
{
var pgp_name = this.refs.pgp_name.getDOMNode().value
var pwd1 = this.refs.pwd1.getDOMNode().value
var pwd2 = this.refs.pwd2.getDOMNode().value
if(pgp_name === "")
{
this.setState({error:"please fill in a name"});
return;
}
if(pwd1 === "")
{
this.setState({error:"please fill in a password"});
return;
}
if(pwd1 !== pwd2)
{
this.setState({error:"passwords do not match"});
return;
}
req.data["pgp_name"] = pgp_name;
req.data["pgp_password"] = pwd1;
}
if(this.refs.cb_hidden.getDOMNode().checked)
{
var addr = this.refs.tor_addr.getDOMNode().value
var port = this.refs.tor_port.getDOMNode().value
if(addr === "" || port === "")
{
this.setState({error:"please fill in hidden adress and hidden port"});
return;
}
req.data["hidden_adress"] = addr;
req.data["hidden_port"] = port;
}
var c = this;
RS.request(req, function(resp){
if(resp.returncode === "ok")
{
c.setState({state:"waiting_end", msg:"created account"});
}
else
{
c.setState({state:"display", error:"failed to create account: "+resp.debug_msg})
}
});
this.setState({state:"waiting"});
},
render: function(){
var c = this;
if(this.state.state === "waiting")
{
return(<div>
<p>please wait a second...</p>
</div>);
//return(<p>Retroshare is initialising... please wait...</p>);
}
//else if(this.state.data.runstate === "waiting_account_select")
else if(this.state.state === "display")
{
if(this.data.runstate === "waiting_account_select")
{
return(
<div>
<div>{this.state.error}</div>
{
this.state.display_data.map(function(loc){
if(loc.peer_id)
return <div className="btn2" key={loc.id} onClick={function(){c.selectAccount(loc.id);}}
>{loc.name} ({loc.location})</div>;
else
return <div className="btn2" key={loc.id} onClick={function(){c.setupCreateLocation(loc.pgp_id, loc.name);}}
>{loc.name}</div>;
})
}
<div
onDragOver={function(event){/*important: block default event*/event.preventDefault();}}
onDrop={this.onDrop}
className="btn2"
>drag and drop a profile file here</div>
<div className="btn2" onClick={function(){c.setupCreateLocation();}}>create new profile</div>
<div onClick={this.shutdown} className="btn2">shut down Retroshare</div>
</div>);
}
else
{
<div>
<p>This is the login page. It has no use at the moment, because Retroshare is aleady running.</p>
</div>
}
}
else if(this.state.state === "create_location")
{
return(
<div>
<div>Create a new node</div>
{function(){
if(c.data.pgp_id !== undefined)
return <div>
Create new nodw with pgp_id {c.data.pgp_id}
<input type="password" ref="pwd1" placeholder="password"/>
</div>
else
return <div>
<input type="text" ref="pgp_name" placeholder="name"/>
<input type="password" ref="pwd1" placeholder="password"/>
<input type="password" ref="pwd2" placeholder="password (repeat)"/>
</div>;
}()}
<input className="checkbox" type="checkbox" ref="cb_hidden"
onClick={function(){c.setState({hidden_node:c.refs.cb_hidden.getDOMNode().checked})}}
/>TOR hidden node<br/>
{function(){if(c.state.hidden_node)
return <div>
<input type="text" ref="tor_addr" placeholder="tor address"/>
<input type="text" ref="tor_port" placeholder="hidden service port"/>
</div>;}()
}
<div>{this.state.error}</div>
<div className="btn2" onClick={c.update}>cancel</div>
<div className="btn2" onClick={c.submitLoc}>go</div>
</div>
);
}
else
{
return(
<div>
<div onClick={this.shutdown} className="btn">shutdown Retroshare</div>
</div>
);
}
},
});
var Menu = React.createClass({
mixins: [SignalSlotMixin],
getInitialState: function(){
@ -772,13 +1075,13 @@ var Menu = React.createClass({
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "main"});}}>
Start
</div>
{function(){
{/*function(){
if(outer.props.fullcontrol)
return (<div className="btn2" onClick={function(){outer.emit("change_url", {url: "login"});}}>
Login
</div>);
else return <div></div>;
}()}
}()*/}
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "friends"});}}>
Friends
</div>
@ -836,6 +1139,82 @@ var TestWidget = React.createClass({
},
});
var FileUploadWidget = React.createClass({
getInitialState: function(){
// states:
// waiting_user waiting_upload upload_ok upload_failed
return {state: "waiting_user", file_name: "", file_id: 0};
},
onDrop: function(event)
{
console.log(event.dataTransfer.files);
event.preventDefault();
this.setState({state:"waiting_upload", file_name: event.dataTransfer.files[0].name});
var reader = new FileReader();
var xhr = new XMLHttpRequest();
var self = this;
xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var percentage = Math.round((e.loaded * 100) / e.total);
console.log("progress:"+percentage);
}
}, false);
var widget = this;
xhr.onreadystatechange = function(){
if (xhr.readyState === 4) {
if(xhr.status !== 200)
{
console.log("upload failed status="+xhr.status);
widget.setState({state: "upload_failed"});
return;
}
//console.log("upload ok");
//console.log(JSON.parse(xhr.responseText));
var resp = JSON.parse(xhr.responseText);
if(resp.ok)
{
widget.setState({state:"upload_ok", file_id: resp.id});
// tell parent about successful upload
if(widget.props.onUploadReady)
widget.props.onUploadReady(resp.id);
}
else
widget.setState({state:"upload_fail"});
}
};
xhr.open("POST", upload_url);
reader.onload = function(evt) {
xhr.send(evt.target.result);
};
// must read as array buffer, to preserve binary data as it is
reader.readAsArrayBuffer(event.dataTransfer.files[0]);
},
render: function(){
var text = "bug-should not happen";
if(this.state.state ==="waiting_user")
text = "drop a file";
if(this.state.state ==="waiting_upload")
text = "File " + this.state.file_name + " is being uploaded";
if(this.state.state ==="upload_ok")
text = "File " + this.state.file_name + " uploaded ok. file_id=" + this.state.file_id;
if(this.state.state ==="upload_fail")
text = "Could not upload file" + this.state.file_name;
return (
<div
onDragOver={function(event){/*important: block default event*/event.preventDefault();}}
onDrop={this.onDrop}
>
{text}
</div>
);
},
});
var MainWidget = React.createClass({
mixins: [SignalSlotMixin, AutoUpdateMixin],
getPath: function(){
@ -872,48 +1251,49 @@ var MainWidget = React.createClass({
}
if(this.state.data.runstate === "waiting_init" || this.state.data.runstate === "waiting_account_select")
{
mainpage = <LoginWidget/>;
//mainpage = <LoginWidget/>;
mainpage = <LoginWidget2/>;
}
if(this.state.page === "testing")
{
//mainpage = <TestWidget/>;
//mainpage = <FileUploadWidget/>;
mainpage = <LoginWidget2/>;
}
if(this.state.data.runstate === "running_ok" || this.state.data.runstate ==="running_ok_no_full_control")
{
if(this.state.page === "main")
{
mainpage = <div><p>
A new webinterface for Retroshare. Build with react.js.
React allows to build a modern and user friendly single page application.
The component system makes this very simple.
Updating the GUI is also very simple: one React mixin can handle updating for all components.
</p>
</div>;
}
if(this.state.page === "friends")
{
mainpage = <Peers3 />;
}
if(this.state.page === "downloads")
{
mainpage = <DownloadsWidget/>;
}
if(this.state.page === "search")
{
mainpage = <SearchWidget/>;
}
if(this.state.page === "add_friend")
{
mainpage = <AddPeerWidget/>;
}
if(this.state.page === "login")
{
mainpage = <LoginWidget/>;
}
if(this.state.page === "menu")
{
mainpage = <Menu fullcontrol = {this.state.data.runstate === "running_ok"}/>;
}
if(this.state.page === "testwidget")
{
mainpage = <TestWidget/>;
}
if(this.state.page === "main")
{
mainpage = <div><p>
A new webinterface for Retroshare. Build with react.js.
React allows to build a modern and user friendly single page application.
The component system makes this very simple.
Updating the GUI is also very simple: one React mixin can handle updating for all components.
</p>
</div>;
}
if(this.state.page === "friends")
{
mainpage = <Peers3 />;
}
if(this.state.page === "downloads")
{
mainpage = <DownloadsWidget/>;
}
if(this.state.page === "search")
{
mainpage = <SearchWidget/>;
}
if(this.state.page === "add_friend")
{
mainpage = <AddPeerWidget/>;
}
if(this.state.page === "menu")
{
mainpage = <Menu fullcontrol = {this.state.data.runstate === "running_ok"}/>;
}
}
var menubutton = <div onClick={function(){outer.emit("change_url", {url: "menu"});}} className="btn2">&lt;- menu</div>;

View File

@ -51,9 +51,11 @@ td{
padding: 0.1em;
}
.btn2{
.btn2, .box{
border-style: solid;
border-color: lime;
/*border-color: lime;*/
border-color: limeGreen;
/*border-width: 1px;*/
border-radius: 3mm;
padding: 2mm;
font-size: 10mm;

View File

@ -4,6 +4,7 @@ RS.start();
var api_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/api/v2/";
var filestreamer_url = window.location.protocol + "//" +window.location.hostname + ":" + window.location.port + "/fstream/";
var upload_url = window.location.protocol + "//" +window.location.hostname + ":" + window.location.port + "/upload/";
// livereload
function start_live_reload()
@ -716,10 +717,11 @@ var AccountSelectWidget = React.createClass({
else
return(
<div>
<div><p>select a location to log in</p></div>
{this.state.data.map(function(location){
return <div className="btn2" key={location.id} onClick ={function(){component.selectAccount(location.id);}}>{location.name} ({location.location})</div>;
})}
{
this.state.data.map(function(location){
return <div className="btn2" key={location.id} onClick ={function(){component.selectAccount(location.id);}}>login {location.name} ({location.location})</div>;
})
}
</div>
);
},
@ -757,6 +759,307 @@ var LoginWidget = React.createClass({
},
});
var LoginWidget2 = React.createClass({
debug: function(msg){
console.log(msg);
},
getInitialState: function(){
return {display_data:[], state: "waiting", hidden_node: false, error: ""};
},
componentDidMount: function()
{
this.update();
// FOR TESTING
//this.setState({state: "create_location"});
},
// this component depends on three resources
// have to fecth all, before can display anything
data: {},
clear_data: function()
{
this.debug("clear_data()");
data =
{
have_runstate: false,
runstate: "",
have_identities: false,
identities: [],
have_locations: false,
locations: [],
pgp_id: undefined,
pgp_name: undefined,
};
},
update: function()
{
this.debug("update()");
var c = this;
c.clear_data();
function check_data()
{
if(c.data.have_runstate && c.data.have_locations && c.data.have_identities)
{
c.debug("update(): check_data(): have all data, will change to display mode");
var data = [];
var pgp_ids_with_loc = [];
function pgp_id_has_location(pgp_id)
{
for(var i in pgp_ids_with_loc)
{
if(pgp_id === pgp_ids_with_loc[i])
return true;
}
return false;
}
// collect all pgp ids with location
// collect location data
for(var i in c.data.locations)
{
if(!pgp_id_has_location(c.data.locations[i].pgp_id))
pgp_ids_with_loc.push(c.data.locations[i].pgp_id);
data.push(c.data.locations[i]);
}
// collect pgp data without location
for(var i in c.data.identities)
{
if(!pgp_id_has_location(c.data.identities[i].pgp_id))
data.push(c.data.identities[i]);
}
c.setState({
display_data: data,
state: "display",
});
}
}
RS.request({path:"control/runstate"}, function(resp){
c.debug("update(): got control/runstate");
if(resp.returncode === "ok"){
c.data.have_runstate = true;
c.data.runstate = resp.data.runstate;
check_data();
}
});
RS.request({path:"control/identities"}, function(resp){
c.debug("update(): got control/identities");
if(resp.returncode === "ok"){
c.data.have_identities = true;
c.data.identities = resp.data;
check_data();
}
});
RS.request({path:"control/locations"}, function(resp){
c.debug("update(): got control/locations");
if(resp.returncode === "ok"){
c.data.have_locations = true;
c.data.locations = resp.data;
check_data();
}
});
},
shutdown: function(){
RS.request({path: "control/shutdown"});
},
selectAccount: function(id){
this.debug("login with id="+id);
RS.request({path: "control/login", data:{id: id}});
var state = this.state;
state.mode = "wait";
this.setState(state);
},
onDrop: function(event)
{
this.debug("onDrop()");
this.debug(event.dataTransfer.files);
event.preventDefault();
var reader = new FileReader();
var widget = this;
var c = this;
reader.onload = function(evt) {
c.debug("onDrop(): file loaded");
RS.request({path:"control/import_pgp", data:{key_string:evt.target.result}}, c.importCallback);
};
reader.readAsText(event.dataTransfer.files[0]);
this.setState({state:"waiting"});
},
importCallback: function(resp)
{
this.debug("importCallback()");
console.log(resp);
if(resp.returncode === "ok")
{
this.debug("import ok");
this.setState({error: "import ok"});
}
else
{
this.setState({error: "import error"});
}
this.update();
},
selectAccount: function(id){
console.log("login with id="+id)
RS.request({path: "control/login", data:{id: id}});
this.setState({state: "waiting"});
},
setupCreateLocation: function(pgp_id, pgp_name)
{
this.data.pgp_id = pgp_id;
this.data.pgp_name = pgp_name;
this.setState({state: "create_location"});
},
submitLoc: function()
{
var req = {
path: "control/create_location",
data: {
ssl_name: "nogui-webui"
}
};
if(this.data.pgp_id !== undefined)
{
req.data["pgp_password"] = this.refs.pwd1.getDOMNode().value;
req.data["pgp_id"] = this.data.pgp_id;
}
else
{
var pgp_name = this.refs.pgp_name.getDOMNode().value
var pwd1 = this.refs.pwd1.getDOMNode().value
var pwd2 = this.refs.pwd2.getDOMNode().value
if(pgp_name === "")
{
this.setState({error:"please fill in a name"});
return;
}
if(pwd1 === "")
{
this.setState({error:"please fill in a password"});
return;
}
if(pwd1 !== pwd2)
{
this.setState({error:"passwords do not match"});
return;
}
req.data["pgp_name"] = pgp_name;
req.data["pgp_password"] = pwd1;
}
if(this.refs.cb_hidden.getDOMNode().checked)
{
var addr = this.refs.tor_addr.getDOMNode().value
var port = this.refs.tor_port.getDOMNode().value
if(addr === "" || port === "")
{
this.setState({error:"please fill in hidden adress and hidden port"});
return;
}
req.data["hidden_adress"] = addr;
req.data["hidden_port"] = port;
}
var c = this;
RS.request(req, function(resp){
if(resp.returncode === "ok")
{
c.setState({state:"waiting_end", msg:"created account"});
}
else
{
c.setState({state:"display", error:"failed to create account: "+resp.debug_msg})
}
});
this.setState({state:"waiting"});
},
render: function(){
var c = this;
if(this.state.state === "waiting")
{
return(<div>
<p>please wait a second...</p>
</div>);
//return(<p>Retroshare is initialising... please wait...</p>);
}
//else if(this.state.data.runstate === "waiting_account_select")
else if(this.state.state === "display")
{
if(this.data.runstate === "waiting_account_select")
{
return(
<div>
<div>{this.state.error}</div>
{
this.state.display_data.map(function(loc){
if(loc.peer_id)
return <div className="btn2" key={loc.id} onClick={function(){c.selectAccount(loc.id);}}
>{loc.name} ({loc.location})</div>;
else
return <div className="btn2" key={loc.id} onClick={function(){c.setupCreateLocation(loc.pgp_id, loc.name);}}
>{loc.name}</div>;
})
}
<div
onDragOver={function(event){/*important: block default event*/event.preventDefault();}}
onDrop={this.onDrop}
className="btn2"
>drag and drop a profile file here</div>
<div className="btn2" onClick={function(){c.setupCreateLocation();}}>create new profile</div>
<div onClick={this.shutdown} className="btn2">shut down Retroshare</div>
</div>);
}
else
{
<div>
<p>This is the login page. It has no use at the moment, because Retroshare is aleady running.</p>
</div>
}
}
else if(this.state.state === "create_location")
{
return(
<div>
<div>Create a new node</div>
{function(){
if(c.data.pgp_id !== undefined)
return <div>
Create new nodw with pgp_id {c.data.pgp_id}
<input type="password" ref="pwd1" placeholder="password"/>
</div>
else
return <div>
<input type="text" ref="pgp_name" placeholder="name"/>
<input type="password" ref="pwd1" placeholder="password"/>
<input type="password" ref="pwd2" placeholder="password (repeat)"/>
</div>;
}()}
<input className="checkbox" type="checkbox" ref="cb_hidden"
onClick={function(){c.setState({hidden_node:c.refs.cb_hidden.getDOMNode().checked})}}
/>TOR hidden node<br/>
{function(){if(c.state.hidden_node)
return <div>
<input type="text" ref="tor_addr" placeholder="tor address"/>
<input type="text" ref="tor_port" placeholder="hidden service port"/>
</div>;}()
}
<div>{this.state.error}</div>
<div className="btn2" onClick={c.update}>cancel</div>
<div className="btn2" onClick={c.submitLoc}>go</div>
</div>
);
}
else
{
return(
<div>
<div onClick={this.shutdown} className="btn">shutdown Retroshare</div>
</div>
);
}
},
});
var Menu = React.createClass({
mixins: [SignalSlotMixin],
getInitialState: function(){
@ -772,13 +1075,13 @@ var Menu = React.createClass({
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "main"});}}>
Start
</div>
{function(){
{/*function(){
if(outer.props.fullcontrol)
return (<div className="btn2" onClick={function(){outer.emit("change_url", {url: "login"});}}>
Login
</div>);
else return <div></div>;
}()}
}()*/}
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "friends"});}}>
Friends
</div>
@ -836,6 +1139,82 @@ var TestWidget = React.createClass({
},
});
var FileUploadWidget = React.createClass({
getInitialState: function(){
// states:
// waiting_user waiting_upload upload_ok upload_failed
return {state: "waiting_user", file_name: "", file_id: 0};
},
onDrop: function(event)
{
console.log(event.dataTransfer.files);
event.preventDefault();
this.setState({state:"waiting_upload", file_name: event.dataTransfer.files[0].name});
var reader = new FileReader();
var xhr = new XMLHttpRequest();
var self = this;
xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var percentage = Math.round((e.loaded * 100) / e.total);
console.log("progress:"+percentage);
}
}, false);
var widget = this;
xhr.onreadystatechange = function(){
if (xhr.readyState === 4) {
if(xhr.status !== 200)
{
console.log("upload failed status="+xhr.status);
widget.setState({state: "upload_failed"});
return;
}
//console.log("upload ok");
//console.log(JSON.parse(xhr.responseText));
var resp = JSON.parse(xhr.responseText);
if(resp.ok)
{
widget.setState({state:"upload_ok", file_id: resp.id});
// tell parent about successful upload
if(widget.props.onUploadReady)
widget.props.onUploadReady(resp.id);
}
else
widget.setState({state:"upload_fail"});
}
};
xhr.open("POST", upload_url);
reader.onload = function(evt) {
xhr.send(evt.target.result);
};
// must read as array buffer, to preserve binary data as it is
reader.readAsArrayBuffer(event.dataTransfer.files[0]);
},
render: function(){
var text = "bug-should not happen";
if(this.state.state ==="waiting_user")
text = "drop a file";
if(this.state.state ==="waiting_upload")
text = "File " + this.state.file_name + " is being uploaded";
if(this.state.state ==="upload_ok")
text = "File " + this.state.file_name + " uploaded ok. file_id=" + this.state.file_id;
if(this.state.state ==="upload_fail")
text = "Could not upload file" + this.state.file_name;
return (
<div
onDragOver={function(event){/*important: block default event*/event.preventDefault();}}
onDrop={this.onDrop}
>
{text}
</div>
);
},
});
var MainWidget = React.createClass({
mixins: [SignalSlotMixin, AutoUpdateMixin],
getPath: function(){
@ -872,48 +1251,49 @@ var MainWidget = React.createClass({
}
if(this.state.data.runstate === "waiting_init" || this.state.data.runstate === "waiting_account_select")
{
mainpage = <LoginWidget/>;
//mainpage = <LoginWidget/>;
mainpage = <LoginWidget2/>;
}
if(this.state.page === "testing")
{
//mainpage = <TestWidget/>;
//mainpage = <FileUploadWidget/>;
mainpage = <LoginWidget2/>;
}
if(this.state.data.runstate === "running_ok" || this.state.data.runstate ==="running_ok_no_full_control")
{
if(this.state.page === "main")
{
mainpage = <div><p>
A new webinterface for Retroshare. Build with react.js.
React allows to build a modern and user friendly single page application.
The component system makes this very simple.
Updating the GUI is also very simple: one React mixin can handle updating for all components.
</p>
</div>;
}
if(this.state.page === "friends")
{
mainpage = <Peers3 />;
}
if(this.state.page === "downloads")
{
mainpage = <DownloadsWidget/>;
}
if(this.state.page === "search")
{
mainpage = <SearchWidget/>;
}
if(this.state.page === "add_friend")
{
mainpage = <AddPeerWidget/>;
}
if(this.state.page === "login")
{
mainpage = <LoginWidget/>;
}
if(this.state.page === "menu")
{
mainpage = <Menu fullcontrol = {this.state.data.runstate === "running_ok"}/>;
}
if(this.state.page === "testwidget")
{
mainpage = <TestWidget/>;
}
if(this.state.page === "main")
{
mainpage = <div><p>
A new webinterface for Retroshare. Build with react.js.
React allows to build a modern and user friendly single page application.
The component system makes this very simple.
Updating the GUI is also very simple: one React mixin can handle updating for all components.
</p>
</div>;
}
if(this.state.page === "friends")
{
mainpage = <Peers3 />;
}
if(this.state.page === "downloads")
{
mainpage = <DownloadsWidget/>;
}
if(this.state.page === "search")
{
mainpage = <SearchWidget/>;
}
if(this.state.page === "add_friend")
{
mainpage = <AddPeerWidget/>;
}
if(this.state.page === "menu")
{
mainpage = <Menu fullcontrol = {this.state.data.runstate === "running_ok"}/>;
}
}
var menubutton = <div onClick={function(){outer.emit("change_url", {url: "menu"});}} className="btn2">&lt;- menu</div>;

View File

@ -783,8 +783,39 @@ bool PGPHandler::importGPGKeyPair(const std::string& filename,RsPgpId& imported_
if(ops_false == ops_keyring_read_from_file(tmp_keyring, ops_true, filename.c_str()))
{
import_error = "PGPHandler::readKeyRing(): cannot read key file. File corrupted?" ;
free(tmp_keyring);
return false ;
}
return checkAndImportKeyPair(tmp_keyring, imported_key_id, import_error);
}
bool PGPHandler::importGPGKeyPairFromString(const std::string &data, RsPgpId &imported_key_id, std::string &import_error)
{
import_error = "" ;
ops_memory_t* mem = ops_memory_new();
ops_memory_add(mem, (unsigned char*)data.data(), data.length());
ops_keyring_t *tmp_keyring = allocateOPSKeyring();
if(ops_false == ops_keyring_read_from_mem(tmp_keyring, ops_true, mem))
{
import_error = "PGPHandler::importGPGKeyPairFromString(): cannot parse key data" ;
free(tmp_keyring);
return false ;
}
return checkAndImportKeyPair(tmp_keyring, imported_key_id, import_error);
}
bool PGPHandler::checkAndImportKeyPair(ops_keyring_t *tmp_keyring, RsPgpId &imported_key_id, std::string &import_error)
{
if(tmp_keyring == 0)
{
import_error = "PGPHandler::checkAndImportKey(): keyring is null" ;
return false;
}
if(tmp_keyring->nkeys != 2)
{
import_error = "PGPHandler::importKeyPair(): file does not contain a valid keypair." ;
@ -923,6 +954,7 @@ bool PGPHandler::importGPGKeyPair(const std::string& filename,RsPgpId& imported_
// 6 - clean
//
ops_keyring_free(tmp_keyring) ;
free(tmp_keyring);
// write public key to disk
syncDatabase();

View File

@ -74,6 +74,7 @@ class PGPHandler
bool haveSecretKey(const RsPgpId& id) const ;
bool importGPGKeyPair(const std::string& filename,RsPgpId& imported_id,std::string& import_error) ;
bool importGPGKeyPairFromString(const std::string& data,RsPgpId& imported_id,std::string& import_error) ;
bool exportGPGKeyPair(const std::string& filename,const RsPgpId& exported_id) const ;
bool availableGPGCertificatesWithPrivateKeys(std::list<RsPgpId>& ids);
@ -152,6 +153,14 @@ class PGPHandler
//
bool validateAndUpdateSignatures(PGPCertificateInfo& cert,const ops_keydata_t *keydata) ;
/** Check public/private key and import them into the keyring
* @param keyring keyring with the new public/private key pair. Will be freed by the function.
* @param imported_key_id PGP id of the imported key
* @param import_error human readbale error message
* @returns true on success
* */
bool checkAndImportKeyPair(ops_keyring_t *keyring, RsPgpId& imported_key_id,std::string& import_error);
const ops_keydata_t *locked_getPublicKey(const RsPgpId&,bool stamp_the_key) const;
const ops_keydata_t *locked_getSecretKey(const RsPgpId&) const ;

View File

@ -318,6 +318,11 @@ bool AuthGPG::importProfile(const std::string& fname,RsPgpId& imported_id,std::s
return PGPHandler::importGPGKeyPair(fname,imported_id,import_error) ;
}
bool AuthGPG::importProfileFromString(const std::string &data, RsPgpId &gpg_id, std::string &import_error)
{
return PGPHandler::importGPGKeyPairFromString(data, gpg_id, import_error);
}
bool AuthGPG::active()
{

View File

@ -164,6 +164,7 @@ class AuthGPG: public p3Config, public RsTickingThread, public PGPHandler
virtual bool getGPGAcceptedList(std::list<RsPgpId> &ids);
virtual bool getGPGSignedList(std::list<RsPgpId> &ids);
virtual bool importProfile(const std::string& filename,RsPgpId& gpg_id,std::string& import_error) ;
virtual bool importProfileFromString(const std::string& data,RsPgpId& gpg_id,std::string& import_error) ;
virtual bool exportProfile(const std::string& filename,const RsPgpId& gpg_id) ;
virtual bool removeKeysFromPGPKeyring(const std::set<RsPgpId> &pgp_ids,std::string& backup_file,uint32_t& error_code) ;

View File

@ -144,6 +144,7 @@ namespace RsAccounts
// PGP Support Functions.
bool ExportIdentity(const std::string& fname,const RsPgpId& pgp_id) ;
bool ImportIdentity(const std::string& fname,RsPgpId& imported_pgp_id,std::string& import_error) ;
bool ImportIdentityFromString(const std::string& data,RsPgpId& imported_pgp_id,std::string& import_error) ;
void GetUnsupportedKeys(std::map<std::string,std::vector<std::string> > &unsupported_keys);
bool CopyGnuPGKeyrings() ;

View File

@ -904,6 +904,11 @@ bool RsAccountsDetail::importIdentity(const std::string& fname,RsPgpId& id,std::
return AuthGPG::getAuthGPG()->importProfile(fname,id,import_error);
}
bool RsAccountsDetail::importIdentityFromString(const std::string &data, RsPgpId &imported_pgp_id, std::string &import_error)
{
return AuthGPG::getAuthGPG()->importProfileFromString(data, imported_pgp_id, import_error);
}
bool RsAccountsDetail::copyGnuPGKeyrings()
{
std::string pgp_dir = PathPGPDirectory() ;
@ -1284,6 +1289,11 @@ bool RsAccounts::ImportIdentity(const std::string& fname,RsPgpId& imported_pg
return rsAccounts->importIdentity(fname,imported_pgp_id,import_error);
}
bool RsAccounts::ImportIdentityFromString(const std::string& data,RsPgpId& imported_pgp_id,std::string& import_error)
{
return rsAccounts->importIdentityFromString(data,imported_pgp_id,import_error);
}
void RsAccounts::GetUnsupportedKeys(std::map<std::string,std::vector<std::string> > &unsupported_keys)
{
return rsAccounts->getUnsupportedKeys(unsupported_keys);

View File

@ -98,6 +98,7 @@ class RsAccountsDetail
// PGP Support Functions.
bool exportIdentity(const std::string& fname,const RsPgpId& pgp_id) ;
bool importIdentity(const std::string& fname,RsPgpId& imported_pgp_id,std::string& import_error) ;
bool importIdentityFromString(const std::string& data,RsPgpId& imported_pgp_id,std::string& import_error) ;
void getUnsupportedKeys(std::map<std::string,std::vector<std::string> > &unsupported_keys);
bool copyGnuPGKeyrings() ;

View File

@ -93,10 +93,10 @@ TerminalApiClient::TerminalApiClient(ApiServer *api):
TerminalApiClient::~TerminalApiClient()
{
//join();
fullstop();
}
void TerminalApiClient::run()
void TerminalApiClient::data_tick()
{
// values in milliseconds
const int MIN_WAIT_TIME = 20; // sleep time must be smaller or equal than the smallest period
@ -121,7 +121,7 @@ void TerminalApiClient::run()
TerminalInput term;
while(isRunning())
while(!shouldStop())
{
// assuming sleep_time >> work_time
// so we don't have to check the absolute time, just sleep every cycle

View File

@ -8,7 +8,7 @@ namespace resource_api {
// - account selection
// - login
// - shutdown
class TerminalApiClient: private RsSingleJobThread{
class TerminalApiClient: private RsTickingThread{
public:
// zero setup: create an instance of this class and destroy it when not needed anymore
// no need to call start or stop or something
@ -17,7 +17,7 @@ public:
~TerminalApiClient();
protected:
// from RsThread
virtual void run(); /* called once the thread is started. Should be overloaded by subclasses. */
virtual void data_tick(); /* called once the thread is started. Should be overloaded by subclasses. */
private:
void waitForResponse(ApiServer::RequestId id);
bool isTokenValid(StateToken st);