__init__.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. '''
  2. searx is free software: you can redistribute it and/or modify
  3. it under the terms of the GNU Affero General Public License as published by
  4. the Free Software Foundation, either version 3 of the License, or
  5. (at your option) any later version.
  6. searx is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU Affero General Public License for more details.
  10. You should have received a copy of the GNU Affero General Public License
  11. along with searx. If not, see < http://www.gnu.org/licenses/ >.
  12. (C) 2015 by Adam Tauber, <asciimoo@gmail.com>
  13. '''
  14. from hashlib import sha256
  15. from importlib import import_module
  16. from os import listdir, makedirs, remove, stat, utime
  17. from os.path import abspath, basename, dirname, exists, join
  18. from shutil import copyfile
  19. from searx import logger, settings, static_path
  20. logger = logger.getChild('plugins')
  21. from searx.plugins import (oa_doi_rewrite,
  22. ahmia_filter,
  23. hash_plugin,
  24. https_rewrite,
  25. infinite_scroll,
  26. self_info,
  27. hostname_replace,
  28. search_on_category_select,
  29. tracker_url_remover,
  30. vim_hotkeys)
  31. required_attrs = (('name', str),
  32. ('description', str),
  33. ('default_on', bool))
  34. optional_attrs = (('js_dependencies', tuple),
  35. ('css_dependencies', tuple))
  36. class Plugin():
  37. default_on = False
  38. name = 'Default plugin'
  39. description = 'Default plugin description'
  40. class PluginStore():
  41. def __init__(self):
  42. self.plugins = []
  43. def __iter__(self):
  44. for plugin in self.plugins:
  45. yield plugin
  46. def register(self, *plugins, external=False):
  47. if external:
  48. plugins = load_external_plugins(plugins)
  49. for plugin in plugins:
  50. for plugin_attr, plugin_attr_type in required_attrs:
  51. if not hasattr(plugin, plugin_attr) or not isinstance(getattr(plugin, plugin_attr), plugin_attr_type):
  52. logger.critical('missing attribute "{0}", cannot load plugin: {1}'.format(plugin_attr, plugin))
  53. exit(3)
  54. for plugin_attr, plugin_attr_type in optional_attrs:
  55. if not hasattr(plugin, plugin_attr) or not isinstance(getattr(plugin, plugin_attr), plugin_attr_type):
  56. setattr(plugin, plugin_attr, plugin_attr_type())
  57. plugin.id = plugin.name.replace(' ', '_')
  58. self.plugins.append(plugin)
  59. def call(self, ordered_plugin_list, plugin_type, request, *args, **kwargs):
  60. ret = True
  61. for plugin in ordered_plugin_list:
  62. if hasattr(plugin, plugin_type):
  63. ret = getattr(plugin, plugin_type)(request, *args, **kwargs)
  64. if not ret:
  65. break
  66. return ret
  67. def load_external_plugins(plugin_names):
  68. plugins = []
  69. for name in plugin_names:
  70. logger.debug('loading plugin: {0}'.format(name))
  71. try:
  72. pkg = import_module(name)
  73. except Exception as e:
  74. logger.critical('failed to load plugin module {0}: {1}'.format(name, e))
  75. exit(3)
  76. pkg.__base_path = dirname(abspath(pkg.__file__))
  77. prepare_package_resources(pkg, name)
  78. plugins.append(pkg)
  79. logger.debug('plugin "{0}" loaded'.format(name))
  80. return plugins
  81. def sync_resource(base_path, resource_path, name, target_dir, plugin_dir):
  82. dep_path = join(base_path, resource_path)
  83. file_name = basename(dep_path)
  84. resource_path = join(target_dir, file_name)
  85. if not exists(resource_path) or sha_sum(dep_path) != sha_sum(resource_path):
  86. try:
  87. copyfile(dep_path, resource_path)
  88. # copy atime_ns and mtime_ns, so the weak ETags (generated by
  89. # the HTTP server) do not change
  90. dep_stat = stat(dep_path)
  91. utime(resource_path, ns=(dep_stat.st_atime_ns, dep_stat.st_mtime_ns))
  92. except:
  93. logger.critical('failed to copy plugin resource {0} for plugin {1}'.format(file_name, name))
  94. exit(3)
  95. # returning with the web path of the resource
  96. return join('plugins/external_plugins', plugin_dir, file_name)
  97. def prepare_package_resources(pkg, name):
  98. plugin_dir = 'plugin_' + name
  99. target_dir = join(static_path, 'plugins/external_plugins', plugin_dir)
  100. try:
  101. makedirs(target_dir, exist_ok=True)
  102. except:
  103. logger.critical('failed to create resource directory {0} for plugin {1}'.format(target_dir, name))
  104. exit(3)
  105. resources = []
  106. if hasattr(pkg, 'js_dependencies'):
  107. resources.extend(map(basename, pkg.js_dependencies))
  108. pkg.js_dependencies = tuple([
  109. sync_resource(pkg.__base_path, x, name, target_dir, plugin_dir)
  110. for x in pkg.js_dependencies
  111. ])
  112. if hasattr(pkg, 'css_dependencies'):
  113. resources.extend(map(basename, pkg.css_dependencies))
  114. pkg.css_dependencies = tuple([
  115. sync_resource(pkg.__base_path, x, name, target_dir, plugin_dir)
  116. for x in pkg.css_dependencies
  117. ])
  118. for f in listdir(target_dir):
  119. if basename(f) not in resources:
  120. resource_path = join(target_dir, basename(f))
  121. try:
  122. remove(resource_path)
  123. except:
  124. logger.critical('failed to remove unused resource file {0} for plugin {1}'.format(resource_path, name))
  125. exit(3)
  126. def sha_sum(filename):
  127. with open(filename, "rb") as f:
  128. file_content_bytes = f.read()
  129. return sha256(file_content_bytes).hexdigest()
  130. plugins = PluginStore()
  131. plugins.register(oa_doi_rewrite)
  132. plugins.register(hash_plugin)
  133. plugins.register(https_rewrite)
  134. plugins.register(infinite_scroll)
  135. plugins.register(self_info)
  136. plugins.register(hostname_replace)
  137. plugins.register(search_on_category_select)
  138. plugins.register(tracker_url_remover)
  139. plugins.register(vim_hotkeys)
  140. # load external plugins
  141. if 'plugins' in settings:
  142. plugins.register(*settings['plugins'], external=True)
  143. if 'enabled_plugins' in settings:
  144. for plugin in plugins:
  145. if plugin.name in settings['enabled_plugins']:
  146. plugin.default_on = True
  147. else:
  148. plugin.default_on = False
  149. # load tor specific plugins
  150. if settings['outgoing'].get('using_tor_proxy'):
  151. plugins.register(ahmia_filter)