OpenCPN Partial API docs
plugin_blacklist.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: Plugin blacklist for plugins which can or should not be loaded
5  * Author: Alec Leamas
6  *
7  ***************************************************************************
8  * Copyright (C) 2022 by Alec Leamas *
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  * This program is distributed in the hope that it will be useful, *
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18  * GNU General Public License for more details. *
19  * *
20  * You should have received a copy of the GNU General Public License *
21  * along with this program; if not, write to the *
22  * Free Software Foundation, Inc., *
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24  **************************************************************************/
25 #include "model/plugin_blacklist.h"
26 
27 #include <algorithm>
28 #include <cctype>
29 #include <regex>
30 #include <unordered_map>
31 #include <vector>
32 
33 #include <wx/translation.h>
34 #include <wx/log.h>
35 
36 #include "model/logger.h"
37 
38 
39 // Work around gnu's major() and minor() macros
40 #ifdef major
41 #undef major
42 #undef minor
43 #endif
44 
45 
46 #ifdef _WIN32
47 static const char SEP = '\\';
48 #else
49 static const char SEP = '/';
50 #endif
51 
53 typedef struct config_block {
54  const char* name;
56  int version_minor;
57  bool hard;
59  const char* message;
60 } config_block;
61 
62 static const char* const STD_HARD_MSG = _(R"(
63 PlugIn %s, version %i.%i was detected.
64 This version is known to be unstable and will not be loaded.
65 Please update this PlugIn using the PlugIn manager master catalog.
66 )");
67 
68 static const char* const STD_SOFT_MSG = _(R"(
69 PlugIn %s, version %i.%i was detected.
70 This version is known to be unstable.
71 Please update this PlugIn using the PlugIn manager master catalog.
72 )");
73 
74 static const char* const OCHART_OBSOLETED_MSG = _(R"(
75 PlugIn %s, version %i.%i was detected.
76 This plugin is obsolete, the o-charts plugin should be used
77 instead. Please uninstall this plugin and install o-charts
78 using the PlugIn manager master catalog.
79 )");
80 
81 
82 static const config_block plugin_blacklist[] = {
83  { "Radar", 0, 95, true, STD_HARD_MSG},
84  { "Watchdog", 1, 0, true, STD_HARD_MSG},
85  { "squiddio", 0, 2, true, STD_HARD_MSG},
86  { "ObjSearch", 0, 3, true, STD_HARD_MSG},
87 #ifdef __WXOSX__
88  { "S63", 0, 6, true, STD_HARD_MSG},
89 #endif
90  { "oeSENC", 99, 99, true, OCHART_OBSOLETED_MSG},
91  { "oernc_pi", 99, 99, true, OCHART_OBSOLETED_MSG},
92  { "oesenc_pi", 99, 99, true, OCHART_OBSOLETED_MSG}
93 };
94 
95 
97 typedef struct block {
98  int major;
99  int minor;
100  plug_status status;
101  const char* message;
102 
103  block()
104  : major(0), minor(0), status(plug_status::unblocked), message("") {};
105 
106  block(int _major, int _minor)
107  : major(_major), minor(_minor), status(plug_status::unblocked),
108  message("")
109  {};
110 
111  block(const struct config_block& cb)
112  : major(cb.version_major), minor(cb.version_minor),
113  status(cb.hard ? plug_status::hard : plug_status::soft),
114  message(cb.message)
115  {};
116 
118  bool is_matching(int _major, int _minor) const {
119  if (major == -1) return true;
120  if (_major == -1) return false;
121  if (_major > major) return false;
122  if (_minor > minor) return false;
123  return true;
124  };
125 
126  plug_data to_plug_data(std::string name) {
127  return plug_data(name, major, minor);
128  }
129 
130 } block;
131 
132 
134 static inline std::string normalize_lib(const std::string& name) {
135  auto libname(name);
136  auto slashpos = libname.rfind(SEP);
137  if (slashpos != std::string::npos) libname = libname.substr(slashpos + 1);
138 #if defined(__WXGTK__) || defined(__WXOSX__)
139  if (libname.find("lib") == 0) libname = libname.substr(3);
140 #endif
141  auto dotpos = libname.rfind('.');
142  if (dotpos != std::string::npos) libname = libname.substr(0, dotpos);
143  return libname;
144 }
145 
146 static std::string to_lower(const std::string& arg) {
147  std::string s(arg);
148  std::transform(s.begin(), s.end(), s.begin(),
149  [](unsigned char c){ return std::tolower(c); });
150  return s;
151 }
152 
153 
155 
156 friend std::unique_ptr<AbstractBlacklist> blacklist_factory();
157 
158 typedef std::unordered_map<std::string, block> block_map;
159 
160 private:
161  PlugBlacklist() {
162  constexpr int list_len = sizeof(plugin_blacklist)/sizeof(config_block);
163  for (int i = 0; i < list_len; i += 1) {
164  m_blocks[plugin_blacklist[i].name] = block(plugin_blacklist[i]);
165  }
166  }
167 
168  block_map m_blocks;
169 
170  block_map::iterator find_block(const std::string& name) {
171  const auto s = to_lower(name);
172  for (auto it = m_blocks.begin(); it != m_blocks.end(); it++) {
173  if (to_lower(it->first) == s) return it;
174  }
175  return m_blocks.end();
176  }
177 
178  bool update_block(const std::string& name, int major, int minor) {
179  bool new_block = false;
180  if (m_blocks.find(name) == m_blocks.end()) {
181  m_blocks[name] = block(major, minor);
182  new_block = true;
183  }
184  m_blocks[name].status = plug_status::unloadable;
185  m_blocks[name].major = major;
186  m_blocks[name].minor = minor;
187  return new_block;
188  }
189 
191  std::string format_message(const std::string msg, const plug_data& data) {
192  int size = std::snprintf(nullptr, 0, msg.c_str(),
193  data.name.c_str(), data.major, data.minor);
194  if (size < 0) {
195  wxLogWarning("Cannot format message for %s", data.name.c_str());
196  return "Internal error: Cannot format message(!)";
197  }
198  std::unique_ptr<char[]> buf(new char[size]);
199  std::snprintf(buf.get(), size, msg.c_str(),
200  data.name.c_str(), data.major, data.minor);
201  return std::string(buf.get(), buf.get() + size - 1);
202  }
203 
204 public:
205 
206  virtual plug_data get_library_data(const std::string& library_file) {
207  std::string filename(normalize_lib(library_file));
208  auto found = find_block(filename);
209  if (found == m_blocks.end()) return plug_data("", -1, -1);
210  return plug_data(found->first, found->second.major, found->second.minor);
211  }
212 
213  plug_status get_status(const std::string& name, int major, int minor) {
214  if (m_blocks.find(name) == m_blocks.end()) return plug_status::unblocked;
215  const auto& b = m_blocks[name];
216  return b.is_matching(major, minor) ? b.status : plug_status::unblocked;
217  }
218 
219  plug_status get_status(const plug_data pd) {
220  return get_status(pd.name, pd.major, pd.minor);
221  }
222 
223  virtual bool mark_unloadable(const std::string& name,
224  int major, int minor) {
225  return update_block(name, major, minor);
226  }
227 
229  bool mark_unloadable(const std::string& path) {
230  auto filename(path);
231  auto slashpos = filename.rfind(SEP);
232  if (slashpos != std::string::npos)
233  filename = filename.substr(slashpos + 1);
234  return update_block(filename, -1, -1);
235  }
236 
237  bool is_loadable(const std::string path) {
238  auto filename(path);
239  auto slashpos = filename.rfind(SEP);
240  if (slashpos != std::string::npos) filename = filename.substr(slashpos + 1);
241 
242  if (m_blocks.find(filename) == m_blocks.end()) return true;
243  return m_blocks[filename].status != plug_status::unloadable;
244  }
245 
246 // gcc 12 bogus regex warning
247 #pragma GCC diagnostic push
248 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
249  std::string get_message(plug_status status, const plug_data& data) {
250  if (status == plug_status::unloadable) {
251  std::string msg(_("Plugin library %s can not be loaded"));
252  msg = std::regex_replace(msg, std::regex("%s"), data.name);
253  return msg;
254  }
255  if (status == plug_status::unblocked) {
256  wxLogMessage("Attempt to get message for unblocked plugin %s",
257  data.name.c_str());
258  return "No applicable message";
259  }
260  auto found = find_block(data.name);
261  if (found == m_blocks.end())
262  return format_message("No known message for %s version %d.%d", data);
263  else
264  return format_message(found->second.message, data);
265  }
266 #pragma GCC diagnostic pop
267 };
268 
269 
270 std::unique_ptr<AbstractBlacklist> blacklist_factory() {
271  return std::unique_ptr<AbstractBlacklist>(new PlugBlacklist());
272 };
273 
274 #ifdef BLACKLIST_TEST
275 // $ export CPPFLAGS="-g -I../include -DBLACKLIST_TEST -D__WXGTK__"
276 // $ make plugin_blacklist
277 // $ ./plugin_blacklist aisradar_pi 0 96
278 // unblocked
279 
280 #include <iostream>
281 int main(int argc, char** argv) {
282 
283  const std::string name(argv[1]);
284  int major = atoi(argv[2]);
285  int minor = atoi(argv[3]);
286  auto blacklist = blacklist_factory();
287  blacklist->mark_unloadable("foo");
288  auto s = blacklist->get_status(name, major, minor);
289  switch (s) {
290  case plug_status::unloadable: std::cout << "unloadable\n"; break;
291  case plug_status::unblocked: std::cout << "unblocked\n"; break;
292  case plug_status::hard: std::cout << "hard\n"; break;
293  case plug_status::soft: std::cout << "soft\n"; break;
294  }
295  auto lib = blacklist->plugin_by_libname(name);
296  std::cout << "found plugin: \"" << lib.name << "\" version: " << lib.major
297  << "." << lib.minor << "\n";
298  exit(0);
299 }
300 
301 #endif // BLACKLIST_TEST
Plugins could be blacklisted in runtime if they are unloadable or in hardcoded, compile-time list.
plug_status get_status(const plug_data pd)
Return status for given official plugin name and version.
bool mark_unloadable(const std::string &path)
Given a path, mark filename as unloadable.
virtual plug_data get_library_data(const std::string &library_file)
Best effort attempt to get data for a library file.
bool is_loadable(const std::string path)
Return true iff plugin (a path) is loadable.
virtual bool mark_unloadable(const std::string &name, int major, int minor)
Given plugin name and version mark it as unloadable.
std::string get_message(plug_status status, const plug_data &data)
Return plugin-specific message, possibly "".
plug_status get_status(const std::string &name, int major, int minor)
Return status for given official plugin name and version.
Runtime representation of a plugin block.
bool is_matching(int _major, int _minor) const
Return true if _major/_minor matches the blocked plugin.
Hardcoded representation of a blocked plugin.
const char * message
If true, unconditional hard block; else load plugin with a warning.
int version_major
Official plugin name as of GetCommonName().