__init__.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. search_on_category_select,
  28. tracker_url_remover,
  29. vim_hotkeys)
  30. required_attrs = (('name', str),
  31. ('description', str),
  32. ('default_on', bool))
  33. optional_attrs = (('js_dependencies', tuple),
  34. ('css_dependencies', tuple))
  35. class Plugin():
  36. default_on = False
  37. name = 'Default plugin'
  38. description = 'Default plugin description'
  39. class PluginStore():
  40. def __init__(self):
  41. self.plugins = []
  42. def __iter__(self):
  43. for plugin in self.plugins:
  44. yield plugin
  45. def register(self, *plugins, external=False):
  46. if external:
  47. plugins = load_external_plugins(plugins)
  48. for plugin in plugins:
  49. for plugin_attr, plugin_attr_type in required_attrs:
  50. if not hasattr(plugin, plugin_attr) or not isinstance(getattr(plugin, plugin_attr), plugin_attr_type):
  51. logger.critical('missing attribute "{0}", cannot load plugin: {1}'.format(plugin_attr, plugin))
  52. exit(3)
  53. for plugin_attr, plugin_attr_type in optional_attrs:
  54. if not hasattr(plugin, plugin_attr) or not isinstance(getattr(plugin, plugin_attr), plugin_attr_type):
  55. setattr(plugin, plugin_attr, plugin_attr_type())
  56. plugin.id = plugin.name.replace(' ', '_')
  57. self.plugins.append(plugin)
  58. def call(self, ordered_plugin_list, plugin_type, request, *args, **kwargs):
  59. ret = True
  60. for plugin in ordered_plugin_list:
  61. if hasattr(plugin, plugin_type):
  62. ret = getattr(plugin, plugin_type)(request, *args, **kwargs)
  63. if not ret:
  64. break
  65. return ret
  66. def load_external_plugins(plugin_names):
  67. plugins = []
  68. for name in plugin_names:
  69. logger.debug('loading plugin: {0}'.format(name))
  70. try:
  71. pkg = import_module(name)
  72. except Exception as e:
  73. logger.critical('failed to load plugin module {0}: {1}'.format(name, e))
  74. exit(3)
  75. pkg.__base_path = dirname(abspath(pkg.__file__))
  76. prepare_package_resources(pkg, name)
  77. plugins.append(pkg)
  78. logger.debug('plugin "{0}" loaded'.format(name))
  79. return plugins
  80. def sync_resource(base_path, resource_path, name, target_dir, plugin_dir):
  81. dep_path = join(base_path, resource_path)
  82. file_name = basename(dep_path)
  83. resource_path = join(target_dir, file_name)
  84. if not exists(resource_path) or sha_sum(dep_path) != sha_sum(resource_path):
  85. try:
  86. copyfile(dep_path, resource_path)
  87. # copy atime_ns and mtime_ns, so the weak ETags (generated by
  88. # the HTTP server) do not change
  89. dep_stat = stat(dep_path)
  90. utime(resource_path, ns=(dep_stat.st_atime_ns, dep_stat.st_mtime_ns))
  91. except:
  92. logger.critical('failed to copy plugin resource {0} for plugin {1}'.format(file_name, name))
  93. exit(3)
  94. # returning with the web path of the resource
  95. return join('plugins/external_plugins', plugin_dir, file_name)
  96. def prepare_package_resources(pkg, name):
  97. plugin_dir = 'plugin_' + name
  98. target_dir = join(static_path, 'plugins/external_plugins', plugin_dir)
  99. try:
  100. makedirs(target_dir, exist_ok=True)
  101. except:
  102. logger.critical('failed to create resource directory {0} for plugin {1}'.format(target_dir, name))
  103. exit(3)
  104. resources = []
  105. if hasattr(pkg, 'js_dependencies'):
  106. resources.extend(map(basename, pkg.js_dependencies))
  107. pkg.js_dependencies = tuple([
  108. sync_resource(pkg.__base_path, x, name, target_dir, plugin_dir)
  109. for x in pkg.js_dependencies
  110. ])
  111. if hasattr(pkg, 'css_dependencies'):
  112. resources.extend(map(basename, pkg.css_dependencies))
  113. pkg.css_dependencies = tuple([
  114. sync_resource(pkg.__base_path, x, name, target_dir, plugin_dir)
  115. for x in pkg.css_dependencies
  116. ])
  117. for f in listdir(target_dir):
  118. if basename(f) not in resources:
  119. resource_path = join(target_dir, basename(f))
  120. try:
  121. remove(resource_path)
  122. except:
  123. logger.critical('failed to remove unused resource file {0} for plugin {1}'.format(resource_path, name))
  124. exit(3)
  125. def sha_sum(filename):
  126. with open(filename, "rb") as f:
  127. file_content_bytes = f.read()
  128. return sha256(file_content_bytes).hexdigest()
  129. plugins = PluginStore()
  130. plugins.register(oa_doi_rewrite)
  131. plugins.register(hash_plugin)
  132. plugins.register(https_rewrite)
  133. plugins.register(infinite_scroll)
  134. plugins.register(self_info)
  135. plugins.register(search_on_category_select)
  136. plugins.register(tracker_url_remover)
  137. plugins.register(vim_hotkeys)
  138. # load external plugins
  139. if 'plugins' in settings:
  140. plugins.register(*settings['plugins'], external=True)
  141. if 'enabled_plugins' in settings:
  142. for plugin in plugins:
  143. if plugin.name in settings['enabled_plugins']:
  144. plugin.default_on = True
  145. else:
  146. plugin.default_on = False
  147. # load tor specific plugins
  148. if settings['outgoing'].get('using_tor_proxy'):
  149. plugins.register(ahmia_filter)