OpenCPN Partial API docs
rest_server.cpp
Go to the documentation of this file.
1 
2 /**************************************************************************
3  * Copyright (C) 2022 David Register *
4  * Copyright (C) 2022-2023 Alec Leamas *
5  * *
6  * This program is free software; you can redistribute it and/or modify *
7  * it under the terms of the GNU General Public License as published by *
8  * the Free Software Foundation; either version 2 of the License, or *
9  * (at your option) any later version. *
10  * *
11  * This program is distributed in the hope that it will be useful, *
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14  * GNU General Public License for more details. *
15  * *
16  * You should have received a copy of the GNU General Public License *
17  * along with this program; if not, write to the *
18  * Free Software Foundation, Inc., *
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
20  **************************************************************************/
21 
24 #include <memory>
25 #include <mutex>
26 #include <string>
27 #include <utility>
28 #include <vector>
29 
30 #include <wx/event.h>
31 #include <wx/filename.h>
32 #include <wx/log.h>
33 #include <wx/string.h>
34 
35 #include "config.h"
36 
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"
45 
46 #include "mongoose.h"
47 #include "observable_evt.h"
48 
51 
52 using namespace std::chrono_literals;
53 
54 static const char* const kHttpAddr = "http://0.0.0.0:8000";
55 static const char* const kHttpsAddr = "http://0.0.0.0:8443";
56 
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@" })--";
62 
64 enum { ORS_START_OF_SESSION, ORS_CHUNK_N, ORS_CHUNK_LAST };
65 
66 struct RestIoEvtData {
67  const enum class Cmd {
68  Ping,
69  Object,
70  CheckWrite,
71  ListRoutes,
72  ActivateRoute,
73  ReverseRoute,
74  PluginMsg,
75  } cmd;
76  const std::string api_key;
77  const std::string source;
78  const std::string id;
79  const bool force;
80  const bool activate;
81 
83  const std::string payload;
84 
86  static RestIoEvtData CreateCmdData(const std::string& key,
87  const std::string& src,
88  const std::string& gpx_data, bool _force,
89  bool _activate) {
90  return RestIoEvtData(Cmd::Object, key, src, gpx_data, "", _force, _activate);
91  }
92 
94  static RestIoEvtData CreatePingData(const std::string& key,
95  const std::string& src) {
96  return {Cmd::Ping, key, src, "", ""};
97  }
98 
100  static RestIoEvtData CreateChkWriteData(const std::string& key,
101  const std::string& src,
102  const std::string& guid) {
103  return {Cmd::CheckWrite, key, src, "", guid};
104  }
105 
107  static RestIoEvtData CreateListRoutesData(const std::string& key,
108  const std::string& src) {
109  return {Cmd::ListRoutes, key, src, "", ""};
110  }
111 
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, ""};
116  }
117 
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};
123  }
124 
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, ""};
129  }
130 
131 private:
132 
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,
136  std::string id)
137  : RestIoEvtData(c, key, src, _payload, id, false, false) {}
138 
139 };
140 
142 static inline std::string HttpVarToString(const struct mg_str& query,
143  const char* var) {
144  std::string string;
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);
147  return string;
148 }
149 
150 static void PostEvent(RestServer* parent,
151  const std::shared_ptr<RestIoEvtData>& evt_data, int id) {
152  auto evt = new ObservedEvt(REST_IO_EVT, id);
153  evt->SetSharedPtr(evt_data);
154  parent->QueueEvent(evt);
155  if (!dynamic_cast<wxApp*>(wxAppConsole::GetInstance())) {
156  wxTheApp->ProcessPendingEvents();
157  }
158 }
159 
160 static void HandleRxObject(struct mg_connection* c, struct mg_http_message* hm,
161  RestServer* parent) {
162  int MID = ORS_CHUNK_N;
163 
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;
169  if (hm->chunk.len)
170  xml_content = std::string(hm->chunk.ptr, hm->chunk.len);
171  else {
172  MID = ORS_CHUNK_LAST;
173  }
174  mg_http_delete_chunk(c, hm);
175  parent->UpdateReturnStatus(RestServerResult::Void);
176 
177  if (!source.empty()) {
178  assert(parent && "Null parent pointer");
179  auto data_ptr =
180  std::make_shared<RestIoEvtData>(RestIoEvtData::CreateCmdData(
181  api_key, source, xml_content, !force.empty(), !activate.empty()));
182  PostEvent(parent, data_ptr, MID);
183  }
184  if (MID == ORS_CHUNK_LAST) {
185  std::unique_lock<std::mutex> lock{parent->ret_mutex};
186  bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
187  return parent->GetReturnStatus() != RestServerResult::Void;
188  });
189  if (!r) wxLogWarning("Timeout waiting for REST server condition");
190  mg_http_reply(c, 200, "", "{\"result\": %d}\n", parent->GetReturnStatus());
191  }
192 }
193 
194 static void HandlePing(struct mg_connection* c, struct mg_http_message* hm,
195  RestServer* parent) {
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");
200  parent->UpdateReturnStatus(RestServerResult::Void);
201  auto data_ptr = std::make_shared<RestIoEvtData>(
202  RestIoEvtData::CreatePingData(api_key, source));
203  PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
204  std::unique_lock<std::mutex> lock{parent->ret_mutex};
205  bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
206  return parent->GetReturnStatus() != RestServerResult::Void;
207  });
208  if (!r) wxLogWarning("Timeout waiting for REST server condition");
209  }
210  mg_http_reply(c, 200, "", "{\"result\": %d, \"version\": \"%s\"}\n",
211  parent->GetReturnStatus(), VERSION_FULL);
212 }
213 
214 static void HandleWritable(struct mg_connection* c, struct mg_http_message* hm,
215  RestServer* parent) {
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");
221  parent->UpdateReturnStatus(RestServerResult::Void);
222  auto data_ptr = std::make_shared<RestIoEvtData>(
223  RestIoEvtData::CreateChkWriteData(apikey, source, guid));
224  PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
225  std::unique_lock<std::mutex> lock{parent->ret_mutex};
226  bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
227  return parent->GetReturnStatus() != RestServerResult::Void;
228  });
229  if (!r) wxLogWarning("Timeout waiting for REST server condition");
230  }
231  mg_http_reply(c, 200, "", "{\"result\": %d}\n", parent->GetReturnStatus());
232 }
233 
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");
240  parent->UpdateReturnStatus(RestServerResult::Void);
241  auto data_ptr = std::make_shared<RestIoEvtData>(
242  RestIoEvtData::CreateListRoutesData(apikey, source));
243  PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
244  std::unique_lock<std::mutex> lock{parent->ret_mutex};
245  bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
246  return parent->GetReturnStatus() != RestServerResult::Void;
247  });
248  if (!r) wxLogWarning("Timeout waiting for REST server condition");
249  }
250  mg_http_reply(c, 200, "", parent->m_reply_body.c_str());
251 }
252 
253 static void HandleActivateRoute(struct mg_connection* c,
254  struct mg_http_message* hm,
255  RestServer* parent) {
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");
261  parent->UpdateReturnStatus(RestServerResult::Void);
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};
266  bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
267  return parent->GetReturnStatus() != RestServerResult::Void;
268  });
269  if (!r) wxLogWarning("Timeout waiting for REST server condition");
270  }
271  mg_http_reply(c, 200, "", "{\"result\": %d}\n", parent->GetReturnStatus());
272 }
273 
274 static void HandlePluginMsg(struct mg_connection* c,
275  struct mg_http_message* hm,
276  RestServer* parent) {
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;
282  std::string content;
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");
287  parent->UpdateReturnStatus(RestServerResult::Void);
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};
293  bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
294  return parent->GetReturnStatus() != RestServerResult::Void;
295  });
296  if (!r) wxLogWarning("Timeout waiting for REST server condition");
297  mg_http_reply(c, 200, "", "{\"result\": %d}\n",
298  parent->GetReturnStatus());
299  }
300  }
301 }
302 
303 static void HandleReverseRoute(struct mg_connection* c,
304  struct mg_http_message* hm,
305  RestServer* parent) {
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");
311  parent->UpdateReturnStatus(RestServerResult::Void);
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};
316  bool r = parent->return_status_cv.wait_for(lock, 10s, [&] {
317  return parent->GetReturnStatus() != RestServerResult::Void;
318  });
319  if (!r) wxLogWarning("Timeout waiting for REST server condition");
320  }
321  mg_http_reply(c, 200, "", "{\"result\": %d}\n", parent->GetReturnStatus());
322 }
323 
324 // We use the same event handler function for HTTP and HTTPS connections
325 // fn_data is NULL for plain HTTP, and non-NULL for HTTPS
326 static void fn(struct mg_connection* c, int ev, void* ev_data, void* fn_data) {
327  auto parent = static_cast<RestServer*>(fn_data);
328 
329  if (ev == MG_EV_ACCEPT /*&& fn_data != NULL*/) {
330  struct mg_tls_opts opts = {0};
331  opts.ca = nullptr; // "cert.pem" Uncomment to enable two-way SSL
332  opts.cert = parent->m_cert_file.c_str();
333  opts.certkey = parent->m_key_file.c_str();
334  opts.ciphers = nullptr;
335  mg_tls_init(c, &opts);
336  } else if (ev == MG_EV_TLS_HS) { // Think of this as "start of session"
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);
358  } else {
359  mg_http_reply(c, 404, "", "url: not found\n");
360  }
361  }
362 }
363 
364 std::string RestResultText(RestServerResult result) {
365  wxString s;
366  switch (result) {
367  case RestServerResult::NoError:
368  s = _("No error");
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");
383  }
384  return s.ToStdString();
385 }
386 
387 //========================================================================
388 /* RestServer implementation */
389 
390 RestServer::IoThread::IoThread(RestServer& parent, std::string ip)
391  : run_flag(-1), m_parent(parent), m_server_ip(std::move(ip)) {}
392 
393 void RestServer::IoThread::Run() {
394  run_flag = 1;
395  struct mg_mgr mgr = {0}; // Event manager
396  mg_log_set(MG_LL_DEBUG); // Set log level
397  mg_mgr_init(&mgr); // Initialise event manager
398 
399  // Create HTTPS listener
400  MESSAGE_LOG << "Listening on " << m_server_ip << "\n";
401  mg_http_listen(&mgr, m_server_ip.c_str(), fn, &m_parent);
402 
403  while (run_flag > 0) mg_mgr_poll(&mgr, 200); // Infinite event loop
404  mg_mgr_free(&mgr);
405  run_flag = -1;
406  m_parent.m_exit_sem.Post();
407 }
408 
409 void RestServer::IoThread::Stop() { run_flag = 0; }
410 
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;
415  }
416  return r == wxSEMA_NO_ERROR;
417 }
418 
419 RestServer::Apikeys RestServer::Apikeys::Parse(const std::string& s) {
420  Apikeys apikeys;
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];
427  }
428  }
429  return apikeys;
430 }
431 
432 std::string RestServer::Apikeys::ToString() const {
433  std::stringstream ss;
434  for (const auto& it : *this) ss << it.first << ":" << it.second << ";";
435  return ss.str();
436 }
437 
438 void RestServer::UpdateReturnStatus(RestServerResult result) {
439  {
440  std::lock_guard<std::mutex> lock{ret_mutex};
441  return_status = result;
442  }
443  return_status_cv.notify_one();
444 }
445 
446 RestServer::RestServer(RestServerDlgCtx ctx, RouteCtx route_ctx, bool& portable)
447  : m_exit_sem(0, 1),
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),
453  m_overwrite(false),
454  m_io_thread(*this, m_endpoint),
455  m_pincode(Pincode::Create()) {
456  // Prepare the wxEventHandler to accept events from the io thread
457  Bind(REST_IO_EVT, &RestServer::HandleServerMessage, this);
458 }
459 
460 RestServer::~RestServer() { StopServer(); }
461 
462 bool RestServer::StartServer(const fs::path& certificate_location) {
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();
466 
467  // Load persistent config info and kick off the Server thread
468  LoadConfig();
469  if (!m_std_thread.joinable()) {
470  m_std_thread = std::thread([&]() { m_io_thread.Run(); });
471  }
472  return true;
473 }
474 
476  wxLogDebug("Stopping REST service");
477  // Kill off the IO Thread if alive
478  if (m_std_thread.joinable()) {
479  wxLogDebug("Stopping io thread");
480  m_io_thread.Stop();
481  m_io_thread.WaitUntilStopped();
482  m_std_thread.join();
483  }
484 }
485 
486 bool RestServer::LoadConfig() {
487  TheBaseConfig()->SetPath("/Settings/RestServer");
488  wxString key_string;
489  TheBaseConfig()->Read("ServerKeys", &key_string);
490  m_key_map = Apikeys::Parse(key_string.ToStdString());
491  TheBaseConfig()->Read("ServerOverwriteDuplicates", &m_overwrite, false);
492  return true;
493 }
494 
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();
500  return true;
501 }
502 
503 bool RestServer::CheckApiKey(const RestIoEvtData& evt_data) {
504  // Look up the api key in the hash map. If found, we are done.
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;
507  }
508  // Need a new PIN confirmation, add it to map and persist
509  m_pincode = Pincode::Create();
510  std::string new_api_key = m_pincode.Hash();
511  if (evt_data.api_key.size() < 10) // client sends old-style keys
512  new_api_key = m_pincode.CompatHash();
513 
514  m_key_map[evt_data.source] = new_api_key;
515  SaveConfig();
516 
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();
522  m_pin_dialog = m_dlg_ctx.run_pincode_dlg(ss.str(), m_pincode.ToString());
523 
524  return false;
525 }
526 
527 void RestServer::HandleServerMessage(ObservedEvt& event) {
528  auto evt_data = UnpackEvtPointer<RestIoEvtData>(event);
529  m_reply_body = "";
530  switch (event.GetId()) {
531  case ORS_START_OF_SESSION:
532  // Prepare a temp file to catch chuncks that might follow
533  m_upload_path = wxFileName::CreateTempFileName("ocpn_tul").ToStdString();
534 
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(); // reset for next time.
539  return;
540  }
541  return;
542  case ORS_CHUNK_N:
543  // Stream out to temp file
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());
546  }
547  return;
548  case ORS_CHUNK_LAST:
549  // Cancel existing dialog and close temp file
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();
552  break;
553  }
554 
555  if (!CheckApiKey(*evt_data)) {
556  UpdateReturnStatus(RestServerResult::NewPinRequested);
557  return;
558  }
559 
560  switch (evt_data->cmd) {
561  case RestIoEvtData::Cmd::Ping:
562  UpdateReturnStatus(RestServerResult::NoError);
563  return;
564  case RestIoEvtData::Cmd::CheckWrite: {
565  auto guid = evt_data->payload;
566  auto dup = m_route_ctx.find_route_by_guid(guid);
567  if (!dup || evt_data->force || m_overwrite) {
568  UpdateReturnStatus(RestServerResult::NoError);
569  } else {
570  UpdateReturnStatus(RestServerResult::DuplicateRejected);
571  }
572  return;
573  }
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(); // empty for next time
579 
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);
589  }
590  }
591  } else {
592  UpdateReturnStatus(RestServerResult::ObjectParseError);
593  }
594  break;
595  }
596  case RestIoEvtData::Cmd::ListRoutes: {
597  std::stringstream ss;
598  ss << "[";
599  for (auto& r : *pRouteList) {
600  if (ss.str() != "[") ss << ", ";
601  ss << "[ \"" << r->GetGUID() << "\", \"" << r->GetName() << "\"]";
602  }
603  ss << "]";
604  std::string reply(kListRoutesReply);
605  ocpn::replace(reply, "@version@", PACKAGE_VERSION);
606  ocpn::replace(reply, "@routes@", ss.str());
607  m_reply_body = reply;
608  UpdateReturnStatus(RestServerResult::NoError);
609  } break;
610  case RestIoEvtData::Cmd::ActivateRoute: {
611  auto guid = evt_data->payload;
612  activate_route.Notify(guid);
613  UpdateReturnStatus(RestServerResult::NoError);
614  } break;
615  case RestIoEvtData::Cmd::ReverseRoute: {
616  auto guid = evt_data->payload;
617  if (guid.empty()) {
618  UpdateReturnStatus(RestServerResult::ObjectRejected);
619  } else {
620  reverse_route.Notify(guid);
621  UpdateReturnStatus(RestServerResult::NoError);
622  }
623  } break;
624  case RestIoEvtData::Cmd::PluginMsg: {
625  std::ifstream f(m_upload_path);
626  m_upload_path.clear(); // empty for next time
627  std::stringstream ss;
628  ss << f.rdbuf();
629  auto msg = std::make_shared<PluginMsg>(PluginMsg(evt_data->id, ss.str()));
630  NavMsgBus::GetInstance().Notify(msg);
631  UpdateReturnStatus(RestServerResult::NoError);
632  } break;
633  }
634 }
635 
636 void RestServer::HandleRoute(pugi::xml_node object,
637  const RestIoEvtData& evt_data) {
638  Route* route = GPXLoadRoute1(object, false, false, false, 0, true);
639  // Check for duplicate GUID
640  bool add = true;
641  bool overwrite_one = false;
642  Route* duplicate = m_route_ctx.find_route_by_guid(route->GetGUID());
643  if (duplicate) {
644  if (!m_overwrite && !evt_data.force) {
645  auto result = m_dlg_ctx.run_accept_object_dlg(
646  _("The received route already exists on this system.\nReplace?"),
647  _("Always replace objects?"));
648  if (result.status != ID_STG_OK) {
649  add = false;
650  UpdateReturnStatus(RestServerResult::DuplicateRejected);
651  } else {
652  m_overwrite = result.check1_value;
653  overwrite_one = true;
654  SaveConfig();
655  }
656  }
657  }
658  if (add) {
659  if (m_overwrite || overwrite_one || evt_data.force) {
660  // Remove the existing duplicate route before adding new route
661  m_route_ctx.delete_route(duplicate);
662  }
663  // Add the route to the global list
665  if (InsertRouteA(route, &pSet)) {
666  UpdateReturnStatus(RestServerResult::NoError);
667  if (evt_data.activate)
668  activate_route.Notify(route->GetGUID().ToStdString());
669  if (g_pRouteMan) g_pRouteMan->on_routes_update.Notify();
670  } else {
671  UpdateReturnStatus(RestServerResult::RouteInsertError);
672  }
673  }
674  UpdateRouteMgr();
675 }
676 
677 void RestServer::HandleTrack(pugi::xml_node object,
678  const RestIoEvtData& evt_data) {
679  Track* track = GPXLoadTrack1(object, true, false, false, 0);
680  // Check for duplicate GUID
681  bool add = true;
682  bool overwrite_one = false;
683 
684  Track* duplicate = m_route_ctx.find_track_by_guid(track->m_GUID);
685  if (duplicate) {
686  if (!m_overwrite && !evt_data.force) {
687  auto result = m_dlg_ctx.run_accept_object_dlg(
688  _("The received track already exists on this system.\nReplace?"),
689  _("Always replace objects?"));
690 
691  if (result.status != ID_STG_OK) {
692  add = false;
693  UpdateReturnStatus(RestServerResult::DuplicateRejected);
694  } else {
695  m_overwrite = result.check1_value;
696  overwrite_one = true;
697  SaveConfig();
698  }
699  }
700  }
701  if (add) {
702  if (m_overwrite || overwrite_one || evt_data.force) {
703  m_route_ctx.delete_track(duplicate);
704  }
705  // Add the track to the global list
707 
708  if (InsertTrack(track, false))
709  UpdateReturnStatus(RestServerResult::NoError);
710  else
711  UpdateReturnStatus(RestServerResult::RouteInsertError);
712  m_dlg_ctx.top_level_refresh();
713  }
714 }
715 
716 void RestServer::HandleWaypoint(pugi::xml_node object,
717  const RestIoEvtData& evt_data) {
718  RoutePoint* rp =
719  GPXLoadWaypoint1(object, "circle", "", false, false, false, 0);
720  rp->m_bIsolatedMark = true; // This is an isolated mark
721  // Check for duplicate GUID
722  bool add = true;
723  bool overwrite_one = false;
724 
725  RoutePoint* duplicate = m_route_ctx.find_wpt_by_guid(rp->m_GUID);
726  if (duplicate) {
727  if (!m_overwrite && !evt_data.force) {
728  auto result = m_dlg_ctx.run_accept_object_dlg(
729  _("The received waypoint already exists on this system.\nReplace?"),
730  _("Always replace objects?"));
731  if (result.status != ID_STG_OK) {
732  add = false;
733  UpdateReturnStatus(RestServerResult::DuplicateRejected);
734  } else {
735  m_overwrite = result.check1_value;
736  overwrite_one = true;
737  SaveConfig();
738  }
739  }
740  }
741  if (add) {
742  if (m_overwrite || overwrite_one || evt_data.force) {
743  m_route_ctx.delete_waypoint(duplicate);
744  }
745  if (InsertWpt(rp, m_overwrite || overwrite_one || evt_data.force))
746  UpdateReturnStatus(RestServerResult::NoError);
747  else
748  UpdateReturnStatus(RestServerResult::RouteInsertError);
749  m_dlg_ctx.top_level_refresh();
750  }
751 }
752 
753 RestIoEvtData::RestIoEvtData(Cmd c, std::string key, std::string src,
754  std::string _payload, std::string _id, bool _force,
755  bool _activate)
756  : cmd(c),
757  api_key(std::move(key)),
758  source(std::move(src)),
759  id(_id),
760  force(_force),
761  activate(_activate),
762  payload(std::move(_payload)) {}
763 
765  : run_pincode_dlg([](const std::string&, const std::string&) -> wxDialog* {
766  return nullptr;
767  }),
768  update_route_mgr([]() {}),
769  run_accept_object_dlg([](const wxString&, const wxString&) {
770  return AcceptObjectDlgResult();
771  }),
772  top_level_refresh([]() {}) {}
773 
775  : find_route_by_guid(
776  [](const wxString&) { return static_cast<Route*>(nullptr); }),
777  find_track_by_guid(
778  [](const wxString&) { return static_cast<Track*>(nullptr); }),
779  find_wpt_by_guid(
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.
Definition: rest_server.h:237
EventVar activate_route
Notified with a string GUID when user wants to activate a route.
Definition: rest_server.h:234
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.
Definition: ocpn_plugin.h:1652
A random generated int value with accessors for string and hashcode.
Definition: pincode.h:27
static Pincode Create()
Create a new pincode based on a random value.
Definition: pincode.cpp:41
std::string Hash() const
Return a hashvalue string.
Definition: pincode.cpp:54
std::string ToString() const
Return value as string.
Definition: pincode.cpp:48
std::string CompatHash()
Return a hashvalue as computed on 5.8 hosts.
Definition: pincode.cpp:31
A plugin to plugin json message over the REST interface.
Definition: comm_navmsg.h:292
Callbacks for handling dialogs and RouteManager updates.
Definition: rest_server.h:95
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().
Definition: rest_server.h:107
std::function< wxDialog *(const std::string &msg, const std::string &text1)> run_pincode_dlg
Run the "Server wants a pincode" dialog.
Definition: rest_server.h:99
AbstractRestServer implementation and interface to underlying IO thread.
Definition: rest_server.h:241
std::string m_cert_file
Semi-static storage used by IoThread C code.
Definition: rest_server.h:268
std::string m_reply_body
IoThread interface: body of return message, if any.
Definition: rest_server.h:274
RestServerResult GetReturnStatus()
IoThread interface.
Definition: rest_server.h:262
std::condition_variable return_status_cv
IoThread interface: Guards return_status.
Definition: rest_server.h:280
std::mutex ret_mutex
IoThread interface: Guards return_status.
Definition: rest_server.h:277
void StopServer() override
Stop server thread, blocks until completed.
void UpdateRouteMgr() const
IoThread interface.
Definition: rest_server.h:265
void UpdateReturnStatus(RestServerResult r)
IoThread interface.
std::string m_key_file
Semi-static storage used by IoThread C code.
Definition: rest_server.h:271
bool StartServer(const fs::path &certificate_location) override
Start the server thread.
Callbacks for handling routes and tracks.
Definition: rest_server.h:115
RouteCtx()
Dummy stubs constructor.
Definition: route.h:75
EventVar on_routes_update
Notified when list of routes is updated (no data in event)
Definition: routeman.h:196
Definition: track.h:78
std::string RestResultText(RestServerResult result)
RestServerResult string representation.
wxDEFINE_EVENT(REST_IO_EVT, ObservedEvt)
Event from IO thread to main.
Returned status from RunAcceptObjectDlg.
Definition: rest_server.h:83
Definition: cm93.h:174
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
Definition: rest_server.cpp:80
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.
Definition: rest_server.cpp:86
static RestIoEvtData CreatePingData(const std::string &key, const std::string &src)
Create a Cmd::Ping instance:
Definition: rest_server.cpp:94
const std::string payload
GPX data for Cmd::Object, Guid for Cmd::CheckWrite, Activate, Reverse.
Definition: rest_server.cpp:83
const std::string source
Rest API parameter source.
Definition: rest_server.cpp:77
const bool force
rest API parameter force
Definition: rest_server.cpp:79
const std::string api_key
Rest API parameter apikey.
Definition: rest_server.cpp:76
const std::string id
rest API parameter id for PluginMsg.
Definition: rest_server.cpp:78