31 #include <wx/filename.h>
33 #include <wx/string.h>
37 #include "model/config_vars.h"
38 #include "model/comm_navmsg_bus.h"
39 #include "model/logger.h"
40 #include "model/nav_object_database.h"
41 #include "model/ocpn_utils.h"
42 #include "model/pincode.h"
43 #include "model/rest_server.h"
44 #include "model/routeman.h"
47 #include "observable_evt.h"
52 using namespace std::chrono_literals;
54 static const char*
const kHttpAddr =
"http://0.0.0.0:8000";
55 static const char*
const kHttpsAddr =
"http://0.0.0.0:8443";
57 static const char*
const kHttpPortableAddr =
"http://0.0.0.0:8001";
58 static const char*
const kHttpsPortableAddr =
"http://0.0.0.0:8444";
59 static const char*
const kVersionReply = R
"--({ "version": "@version@" })--";
60 static const char*
const kListRoutesReply =
61 R
"--( { "version": "@version@", "routes": "@routes@" })--";
64 enum { ORS_START_OF_SESSION, ORS_CHUNK_N, ORS_CHUNK_LAST };
67 const enum class Cmd {
87 const std::string& src,
88 const std::string& gpx_data,
bool _force,
90 return RestIoEvtData(Cmd::Object, key, src, gpx_data,
"", _force, _activate);
95 const std::string& src) {
96 return {Cmd::Ping, key, src,
"",
""};
101 const std::string& src,
102 const std::string& guid) {
103 return {Cmd::CheckWrite, key, src,
"", guid};
108 const std::string& src) {
109 return {Cmd::ListRoutes, key, src,
"",
""};
112 static RestIoEvtData CreateActivateRouteData(
const std::string& key,
113 const std::string& src,
114 const std::string& guid) {
115 return {Cmd::ActivateRoute, key, src, guid,
""};
118 static RestIoEvtData CreatePluginMsgData(
const std::string& key,
119 const std::string& src,
120 const std::string&
id,
121 const std::string& msg) {
122 return {Cmd::PluginMsg, key, src, msg,
id};
125 static RestIoEvtData CreateReverseRouteData(
const std::string& key,
126 const std::string& src,
127 const std::string& guid) {
128 return {Cmd::ReverseRoute, key, src, guid,
""};
133 RestIoEvtData(Cmd c, std::string key, std::string src, std::string _payload,
134 std::string _id,
bool _force,
bool _activate) ;
135 RestIoEvtData(Cmd c, std::string key, std::string src, std::string _payload,
142 static inline std::string HttpVarToString(
const struct mg_str& query,
145 struct mg_str mgs = mg_http_var(query, mg_str(var));
146 if (mgs.len && mgs.ptr)
string = std::string(mgs.ptr, mgs.len);
151 const std::shared_ptr<RestIoEvtData>& evt_data,
int id) {
153 evt->SetSharedPtr(evt_data);
154 parent->QueueEvent(evt);
155 if (!
dynamic_cast<wxApp*
>(wxAppConsole::GetInstance())) {
156 wxTheApp->ProcessPendingEvents();
160 static void HandleRxObject(
struct mg_connection* c,
struct mg_http_message* hm,
162 int MID = ORS_CHUNK_N;
164 std::string api_key = HttpVarToString(hm->query,
"apikey");
165 std::string source = HttpVarToString(hm->query,
"source");
166 std::string force = HttpVarToString(hm->query,
"force");
167 std::string activate = HttpVarToString(hm->query,
"activate");
168 std::string xml_content;
170 xml_content = std::string(hm->chunk.ptr, hm->chunk.len);
172 MID = ORS_CHUNK_LAST;
174 mg_http_delete_chunk(c, hm);
177 if (!source.empty()) {
178 assert(parent &&
"Null parent pointer");
181 api_key, source, xml_content, !force.empty(), !activate.empty()));
182 PostEvent(parent, data_ptr, MID);
184 if (MID == ORS_CHUNK_LAST) {
185 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
189 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
190 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
194 static void HandlePing(
struct mg_connection* c,
struct mg_http_message* hm,
196 std::string api_key = HttpVarToString(hm->query,
"apikey");
197 std::string source = HttpVarToString(hm->query,
"source");
198 if (!source.empty()) {
199 assert(parent &&
"Null parent pointer");
201 auto data_ptr = std::make_shared<RestIoEvtData>(
203 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
204 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
208 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
210 mg_http_reply(c, 200,
"",
"{\"result\": %d, \"version\": \"%s\"}\n",
214 static void HandleWritable(
struct mg_connection* c,
struct mg_http_message* hm,
216 std::string apikey = HttpVarToString(hm->query,
"apikey");
217 std::string source = HttpVarToString(hm->query,
"source");
218 std::string guid = HttpVarToString(hm->query,
"guid");
219 if (!source.empty()) {
220 assert(parent &&
"Null parent pointer");
222 auto data_ptr = std::make_shared<RestIoEvtData>(
224 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
225 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
229 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
231 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
234 static void HandleListRoutes(
struct mg_connection* c,
235 struct mg_http_message* hm,
RestServer* parent) {
236 std::string apikey = HttpVarToString(hm->query,
"apikey");
237 std::string source = HttpVarToString(hm->query,
"source");
238 if (!source.empty()) {
239 assert(parent &&
"Null parent pointer");
241 auto data_ptr = std::make_shared<RestIoEvtData>(
243 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
244 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
248 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
250 mg_http_reply(c, 200,
"", parent->
m_reply_body.c_str());
253 static void HandleActivateRoute(
struct mg_connection* c,
254 struct mg_http_message* hm,
256 std::string apikey = HttpVarToString(hm->query,
"apikey");
257 std::string source = HttpVarToString(hm->query,
"source");
258 std::string guid = HttpVarToString(hm->query,
"guid");
259 if (!source.empty()) {
260 assert(parent &&
"Null parent pointer");
262 auto data_ptr = std::make_shared<RestIoEvtData>(
263 RestIoEvtData::CreateActivateRouteData(apikey, source, guid));
264 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
265 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
269 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
271 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
274 static void HandlePluginMsg(
struct mg_connection* c,
275 struct mg_http_message* hm,
277 std::string apikey = HttpVarToString(hm->query,
"apikey");
278 std::string source = HttpVarToString(hm->query,
"source");
279 std::string
id = HttpVarToString(hm->query,
"id");
280 std::string message = HttpVarToString(hm->query,
"message");
281 int chunk_status = hm->chunk.len ? ORS_CHUNK_N : ORS_CHUNK_LAST;
283 if (hm->chunk.len) content = std::string(hm->chunk.ptr, hm->chunk.len);
284 mg_http_delete_chunk(c, hm);
285 if (!source.empty()) {
286 assert(parent &&
"Null parent pointer");
288 auto data_ptr = std::make_shared<RestIoEvtData>(
289 RestIoEvtData::CreatePluginMsgData(apikey, source,
id, content));
290 PostEvent(parent, data_ptr, chunk_status);
291 if (chunk_status == ORS_CHUNK_LAST) {
292 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
296 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
297 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n",
303 static void HandleReverseRoute(
struct mg_connection* c,
304 struct mg_http_message* hm,
306 std::string apikey = HttpVarToString(hm->query,
"apikey");
307 std::string source = HttpVarToString(hm->query,
"source");
308 std::string guid = HttpVarToString(hm->query,
"guid");
309 if (!source.empty()) {
310 assert(parent &&
"Null parent pointer");
312 auto data_ptr = std::make_shared<RestIoEvtData>(
313 RestIoEvtData::CreateReverseRouteData(apikey, source, guid));
314 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
315 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
319 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
321 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
326 static void fn(
struct mg_connection* c,
int ev,
void* ev_data,
void* fn_data) {
327 auto parent =
static_cast<RestServer*
>(fn_data);
329 if (ev == MG_EV_ACCEPT ) {
330 struct mg_tls_opts opts = {0};
334 opts.ciphers =
nullptr;
335 mg_tls_init(c, &opts);
336 }
else if (ev == MG_EV_TLS_HS) {
337 PostEvent(parent,
nullptr, ORS_START_OF_SESSION);
338 }
else if (ev == MG_EV_HTTP_CHUNK) {
339 auto hm = (
struct mg_http_message*)ev_data;
340 if (mg_http_match_uri(hm,
"/api/ping")) {
341 HandlePing(c, hm, parent);
342 }
else if (mg_http_match_uri(hm,
"/api/rx_object")) {
343 HandleRxObject(c, hm, parent);
344 }
else if (mg_http_match_uri(hm,
"/api/writable")) {
345 HandleWritable(c, hm, parent);
346 }
else if (mg_http_match_uri(hm,
"/api/get-version")) {
347 std::string reply(kVersionReply);
348 ocpn::replace(reply,
"@version@", PACKAGE_VERSION);
349 mg_http_reply(c, 200,
"", reply.c_str());
350 }
else if (mg_http_match_uri(hm,
"/api/list-routes")) {
351 HandleListRoutes(c, hm, parent);
352 }
else if (mg_http_match_uri(hm,
"/api/activate-route")) {
353 HandleActivateRoute(c, hm, parent);
354 }
else if (mg_http_match_uri(hm,
"/api/reverse-route")) {
355 HandleReverseRoute(c, hm, parent);
356 }
else if (mg_http_match_uri(hm,
"/api/plugin-msg")) {
357 HandlePluginMsg(c, hm, parent);
359 mg_http_reply(c, 404,
"",
"url: not found\n");
367 case RestServerResult::NoError:
369 case RestServerResult::GenericError:
370 s = _(
"Server Generic Error");
371 case RestServerResult::ObjectRejected:
372 s = _(
"Peer rejected object");
373 case RestServerResult::DuplicateRejected:
374 s = _(
"Peer rejected duplicate object");
375 case RestServerResult::RouteInsertError:
376 s = _(
"Peer internal error (insert)");
377 case RestServerResult::NewPinRequested:
378 s = _(
"Peer requests new pincode");
379 case RestServerResult::ObjectParseError:
380 s = _(
"XML parse error");
381 case RestServerResult::Void:
382 s = _(
"Unspecified error");
384 return s.ToStdString();
390 RestServer::IoThread::IoThread(
RestServer& parent, std::string ip)
391 : run_flag(-1), m_parent(parent), m_server_ip(std::move(ip)) {}
393 void RestServer::IoThread::Run() {
395 struct mg_mgr mgr = {0};
396 mg_log_set(MG_LL_DEBUG);
400 MESSAGE_LOG <<
"Listening on " << m_server_ip <<
"\n";
401 mg_http_listen(&mgr, m_server_ip.c_str(), fn, &m_parent);
403 while (run_flag > 0) mg_mgr_poll(&mgr, 200);
406 m_parent.m_exit_sem.Post();
409 void RestServer::IoThread::Stop() { run_flag = 0; }
411 bool RestServer::IoThread::WaitUntilStopped() {
412 auto r = m_parent.m_exit_sem.WaitTimeout(10000);
413 if (r != wxSEMA_NO_ERROR) {
414 WARNING_LOG <<
"Semaphore error: " << r;
416 return r == wxSEMA_NO_ERROR;
419 RestServer::Apikeys RestServer::Apikeys::Parse(
const std::string& s) {
421 auto ip_keys = ocpn::split(s.c_str(),
";");
422 for (
const auto& ip_key : ip_keys) {
423 auto words = ocpn::split(ip_key.c_str(),
":");
424 if (words.size() != 2)
continue;
425 if (apikeys.find(words[0]) == apikeys.end()) {
426 apikeys[words[0]] = words[1];
432 std::string RestServer::Apikeys::ToString()
const {
433 std::stringstream ss;
434 for (
const auto& it : *
this) ss << it.first <<
":" << it.second <<
";";
440 std::lock_guard<std::mutex> lock{
ret_mutex};
441 return_status = result;
448 m_endpoint(portable ? kHttpsPortableAddr : kHttpsAddr),
449 m_dlg_ctx(std::move(ctx)),
450 m_route_ctx(std::move(route_ctx)),
451 return_status(RestServerResult::Void),
452 m_pin_dialog(nullptr),
454 m_io_thread(*this, m_endpoint),
457 Bind(REST_IO_EVT, &RestServer::HandleServerMessage,
this);
463 m_certificate_directory = certificate_location.string();
464 m_cert_file = (certificate_location /
"cert.pem").
string();
465 m_key_file = (certificate_location /
"key.pem").
string();
469 if (!m_std_thread.joinable()) {
470 m_std_thread = std::thread([&]() { m_io_thread.Run(); });
476 wxLogDebug(
"Stopping REST service");
478 if (m_std_thread.joinable()) {
479 wxLogDebug(
"Stopping io thread");
481 m_io_thread.WaitUntilStopped();
486 bool RestServer::LoadConfig() {
487 TheBaseConfig()->SetPath(
"/Settings/RestServer");
489 TheBaseConfig()->Read(
"ServerKeys", &key_string);
490 m_key_map = Apikeys::Parse(key_string.ToStdString());
491 TheBaseConfig()->Read(
"ServerOverwriteDuplicates", &m_overwrite,
false);
495 bool RestServer::SaveConfig() {
496 TheBaseConfig()->SetPath(
"/Settings/RestServer");
497 TheBaseConfig()->Write(
"ServerKeys", wxString(m_key_map.ToString()));
498 TheBaseConfig()->Write(
"ServerOverwriteDuplicates", m_overwrite);
499 TheBaseConfig()->Flush();
503 bool RestServer::CheckApiKey(
const RestIoEvtData& evt_data) {
505 if (m_key_map.find(evt_data.
source) != m_key_map.end()) {
506 if (m_key_map[evt_data.
source] == evt_data.
api_key)
return true;
510 std::string new_api_key = m_pincode.
Hash();
511 if (evt_data.
api_key.size() < 10)
514 m_key_map[evt_data.
source] = new_api_key;
517 std::stringstream ss;
518 ss << evt_data.
source <<
" " << _(
"wants to send you new data.") <<
"\n"
519 << _(
"Please enter the following PIN number on ") << evt_data.
source <<
" "
520 << _(
"to pair with this device") <<
"\n";
521 if (m_pin_dialog) m_pin_dialog->Destroy();
527 void RestServer::HandleServerMessage(
ObservedEvt& event) {
528 auto evt_data = UnpackEvtPointer<RestIoEvtData>(event);
530 switch (event.GetId()) {
531 case ORS_START_OF_SESSION:
533 m_upload_path = wxFileName::CreateTempFileName(
"ocpn_tul").ToStdString();
535 m_ul_stream.open(m_upload_path.c_str(), std::ios::out | std::ios::trunc);
536 if (!m_ul_stream.is_open()) {
537 wxLogMessage(
"REST_server: Cannot open %s for write", m_upload_path);
538 m_upload_path.clear();
544 if (!m_upload_path.empty() && m_ul_stream.is_open()) {
545 m_ul_stream.write(evt_data->
payload.c_str(), evt_data->
payload.size());
550 if (m_pin_dialog) wxQueueEvent(m_pin_dialog,
new wxCloseEvent);
551 if (!m_upload_path.empty() && m_ul_stream.is_open()) m_ul_stream.close();
555 if (!CheckApiKey(*evt_data)) {
560 switch (evt_data->cmd) {
561 case RestIoEvtData::Cmd::Ping:
564 case RestIoEvtData::Cmd::CheckWrite: {
566 auto dup = m_route_ctx.find_route_by_guid(guid);
567 if (!dup || evt_data->
force || m_overwrite) {
574 case RestIoEvtData::Cmd::Object: {
575 pugi::xml_document doc;
576 pugi::xml_parse_result result = doc.load_file(m_upload_path.c_str());
577 if (result.status == pugi::status_ok) {
578 m_upload_path.clear();
580 pugi::xml_node objects = doc.child(
"gpx");
581 for (pugi::xml_node
object = objects.first_child();
object;
582 object =
object.next_sibling()) {
583 if (!strcmp(
object.name(),
"rte")) {
584 HandleRoute(
object, *evt_data);
585 }
else if (!strcmp(
object.name(),
"trk")) {
586 HandleTrack(
object, *evt_data);
587 }
else if (!strcmp(
object.name(),
"wpt")) {
588 HandleWaypoint(
object, *evt_data);
596 case RestIoEvtData::Cmd::ListRoutes: {
597 std::stringstream ss;
599 for (
auto& r : *pRouteList) {
600 if (ss.str() !=
"[") ss <<
", ";
601 ss <<
"[ \"" << r->GetGUID() <<
"\", \"" << r->GetName() <<
"\"]";
604 std::string reply(kListRoutesReply);
605 ocpn::replace(reply,
"@version@", PACKAGE_VERSION);
606 ocpn::replace(reply,
"@routes@", ss.str());
610 case RestIoEvtData::Cmd::ActivateRoute: {
615 case RestIoEvtData::Cmd::ReverseRoute: {
624 case RestIoEvtData::Cmd::PluginMsg: {
625 std::ifstream f(m_upload_path);
626 m_upload_path.clear();
627 std::stringstream ss;
629 auto msg = std::make_shared<PluginMsg>(
PluginMsg(evt_data->
id, ss.str()));
630 NavMsgBus::GetInstance().
Notify(msg);
636 void RestServer::HandleRoute(pugi::xml_node
object,
638 Route* route = GPXLoadRoute1(
object,
false,
false,
false, 0,
true);
641 bool overwrite_one =
false;
642 Route* duplicate = m_route_ctx.find_route_by_guid(route->GetGUID());
644 if (!m_overwrite && !evt_data.
force) {
646 _(
"The received route already exists on this system.\nReplace?"),
647 _(
"Always replace objects?"));
648 if (result.status != ID_STG_OK) {
652 m_overwrite = result.check1_value;
653 overwrite_one =
true;
659 if (m_overwrite || overwrite_one || evt_data.
force) {
661 m_route_ctx.delete_route(duplicate);
665 if (InsertRouteA(route, &pSet)) {
677 void RestServer::HandleTrack(pugi::xml_node
object,
679 Track* track = GPXLoadTrack1(
object,
true,
false,
false, 0);
682 bool overwrite_one =
false;
684 Track* duplicate = m_route_ctx.find_track_by_guid(track->m_GUID);
686 if (!m_overwrite && !evt_data.
force) {
688 _(
"The received track already exists on this system.\nReplace?"),
689 _(
"Always replace objects?"));
691 if (result.status != ID_STG_OK) {
695 m_overwrite = result.check1_value;
696 overwrite_one =
true;
702 if (m_overwrite || overwrite_one || evt_data.
force) {
703 m_route_ctx.delete_track(duplicate);
708 if (InsertTrack(track,
false))
712 m_dlg_ctx.top_level_refresh();
716 void RestServer::HandleWaypoint(pugi::xml_node
object,
719 GPXLoadWaypoint1(
object,
"circle",
"",
false,
false,
false, 0);
720 rp->m_bIsolatedMark =
true;
723 bool overwrite_one =
false;
725 RoutePoint* duplicate = m_route_ctx.find_wpt_by_guid(rp->m_GUID);
727 if (!m_overwrite && !evt_data.
force) {
729 _(
"The received waypoint already exists on this system.\nReplace?"),
730 _(
"Always replace objects?"));
731 if (result.status != ID_STG_OK) {
735 m_overwrite = result.check1_value;
736 overwrite_one =
true;
742 if (m_overwrite || overwrite_one || evt_data.
force) {
743 m_route_ctx.delete_waypoint(duplicate);
745 if (InsertWpt(rp, m_overwrite || overwrite_one || evt_data.
force))
749 m_dlg_ctx.top_level_refresh();
753 RestIoEvtData::RestIoEvtData(Cmd c, std::string key, std::string src,
754 std::string _payload, std::string _id,
bool _force,
757 api_key(std::move(key)),
758 source(std::move(src)),
762 payload(std::move(_payload)) {}
765 : run_pincode_dlg([](const std::string&, const std::string&) -> wxDialog* {
768 update_route_mgr([]() {}),
769 run_accept_object_dlg([](
const wxString&,
const wxString&) {
772 top_level_refresh([]() {}) {}
775 : find_route_by_guid(
776 [](const wxString&) {
return static_cast<Route*
>(
nullptr); }),
778 [](
const wxString&) {
return static_cast<Track*
>(
nullptr); }),
780 [](
const wxString&) {
return static_cast<RoutePoint*
>(
nullptr); }),
781 delete_route([](
Route*) ->
void {}),
782 delete_track([](
Track*) ->
void {}),
783 delete_waypoint([](
RoutePoint*) ->
void {}) {}
EventVar reverse_route
Notified with a string GUID when user wants to reverse a route.
EventVar activate_route
Notified with a string GUID when user wants to activate a route.
const void Notify()
Notify all listeners, no data supplied.
void Notify(std::shared_ptr< const NavMsg > message)
Handle a received message.
Adds a std::shared<void> element to wxCommandEvent.
A random generated int value with accessors for string and hashcode.
static Pincode Create()
Create a new pincode based on a random value.
std::string Hash() const
Return a hashvalue string.
std::string ToString() const
Return value as string.
std::string CompatHash()
Return a hashvalue as computed on 5.8 hosts.
A plugin to plugin json message over the REST interface.
Callbacks for handling dialogs and RouteManager updates.
RestServerDlgCtx()
All dummy stubs constructor.
std::function< AcceptObjectDlgResult(const wxString &msg, const wxString &check1msg)> run_accept_object_dlg
Run the "Accept Object" dialog, returns value from ShowModal().
std::function< wxDialog *(const std::string &msg, const std::string &text1)> run_pincode_dlg
Run the "Server wants a pincode" dialog.
AbstractRestServer implementation and interface to underlying IO thread.
std::string m_cert_file
Semi-static storage used by IoThread C code.
std::string m_reply_body
IoThread interface: body of return message, if any.
RestServerResult GetReturnStatus()
IoThread interface.
std::condition_variable return_status_cv
IoThread interface: Guards return_status.
std::mutex ret_mutex
IoThread interface: Guards return_status.
void StopServer() override
Stop server thread, blocks until completed.
void UpdateRouteMgr() const
IoThread interface.
void UpdateReturnStatus(RestServerResult r)
IoThread interface.
std::string m_key_file
Semi-static storage used by IoThread C code.
bool StartServer(const fs::path &certificate_location) override
Start the server thread.
Callbacks for handling routes and tracks.
RouteCtx()
Dummy stubs constructor.
EventVar on_routes_update
Notified when list of routes is updated (no data in event)
std::string RestResultText(RestServerResult result)
RestServerResult string representation.
wxDEFINE_EVENT(REST_IO_EVT, ObservedEvt)
Event from IO thread to main.
Returned status from RunAcceptObjectDlg.
static RestIoEvtData CreateListRoutesData(const std::string &key, const std::string &src)
Create a Cmd::ListRoutes instance.
static RestIoEvtData CreateChkWriteData(const std::string &key, const std::string &src, const std::string &guid)
Create a Cmd::CheckWrite instance.
const bool activate
rest API parameter activate
static RestIoEvtData CreateCmdData(const std::string &key, const std::string &src, const std::string &gpx_data, bool _force, bool _activate)
Create a Cmd::Object instance.
static RestIoEvtData CreatePingData(const std::string &key, const std::string &src)
Create a Cmd::Ping instance:
const std::string payload
GPX data for Cmd::Object, Guid for Cmd::CheckWrite, Activate, Reverse.
const std::string source
Rest API parameter source.
const bool force
rest API parameter force
const std::string api_key
Rest API parameter apikey.
const std::string id
rest API parameter id for PluginMsg.