__init__.py 6.5 KB

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