2"""With *command engines* administrators can run engines to integrate arbitrary
7 When creating and enabling a ``command`` engine on a public instance, you
8 must be careful to avoid leaking private data.
10The easiest solution is to limit the access by setting ``tokens`` as described
11in section :ref:`private engines`. The engine base is flexible. Only your
12imagination can limit the power of this engine (and maybe security concerns).
17The following options are available:
20 A comma separated list of the elements of the command. A special token
21 ``{{QUERY}}`` tells where to put the search terms of the user. Example:
25 ['ls', '-l', '-h', '{{QUERY}}']
28 A mapping containing a delimiter ``char`` and the *titles* of each element in
32 A dict containing the regular expressions for each result key.
36 The expected type of user search terms. Possible values: ``path`` and
40 Checks if the user provided path is inside the working directory. If not,
41 the query is not executed.
44 Is a list of allowed search terms. If the user submits something which is
45 not included in the list, the query returns an error.
48 A list containing allowed search terms if ``query_type`` is set to ``enum``.
51 The directory where the command has to be executed. Default: ``./``.
54 The character that separates results. Default: ``\\n``.
59The example engine below can be used to find files with a specific name in the
60configured working directory:
66 command: ['find', '.', '-name', '{{QUERY}}']
78from os.path
import expanduser, isabs, realpath, commonprefix
79from shlex
import split
as shlex_split
80from subprocess
import Popen, PIPE
81from threading
import Thread
83from searx
import logger
86engine_type =
'offline'
93environment_variables = {}
94working_dir = realpath(
'.')
95result_separator =
'\n'
96result_template =
'key-value.html'
99_command_logger = logger.getChild(
'command')
100_compiled_parse_regex = {}
106 if 'command' not in engine_settings:
107 raise ValueError(
'engine command : missing configuration key: command')
109 global command, working_dir, delimiter, parse_regex, environment_variables
111 command = engine_settings[
'command']
113 if 'working_dir' in engine_settings:
114 working_dir = engine_settings[
'working_dir']
115 if not isabs(engine_settings[
'working_dir']):
116 working_dir = realpath(working_dir)
118 if 'parse_regex' in engine_settings:
119 parse_regex = engine_settings[
'parse_regex']
120 for result_key, regex
in parse_regex.items():
121 _compiled_parse_regex[result_key] = re.compile(regex, flags=re.MULTILINE)
122 if 'delimiter' in engine_settings:
123 delimiter = engine_settings[
'delimiter']
125 if 'environment_variables' in engine_settings:
126 environment_variables = engine_settings[
'environment_variables']
129def search(query, params):
135 reader_thread = Thread(target=_get_results_from_process, args=(results, cmd, params[
'pageno']))
136 reader_thread.start()
137 reader_thread.join(timeout=timeout)
143 params = shlex_split(query)
160 with Popen(cmd, stdout=PIPE, stderr=PIPE, env=environment_variables)
as process:
161 line = process.stdout.readline()
163 buf = leftover + line.decode(
'utf-8')
164 raw_results = buf.split(result_separator)
166 leftover = raw_results[-1]
167 raw_results = raw_results[:-1]
169 for raw_result
in raw_results:
172 _command_logger.debug(
'skipped result:', raw_result)
175 if start <= count
and count <= end:
176 result[
'template'] = result_template
177 results.append(result)
183 line = process.stdout.readline()
185 return_code = process.wait(timeout=timeout)
187 raise RuntimeError(
'non-zero return code when running command', cmd, return_code)
192 start = (pageno - 1) * 10
201 if query_type ==
'path':
202 query_path = params[-1]
203 query_path = expanduser(query_path)
204 if commonprefix([realpath(query_path), working_dir]) != working_dir:
205 raise ValueError(
'requested path is outside of configured working directory')
206 elif query_type ==
'enum' and len(query_enum) > 0:
208 if param
not in query_enum:
209 raise ValueError(
'submitted query params is not allowed', param,
'allowed params:', query_enum)
213 """Checks if delimiter based parsing or regex parsing is configured correctly"""
215 if 'delimiter' not in engine_settings
and 'parse_regex' not in engine_settings:
216 raise ValueError(
'failed to init settings for parsing lines: missing delimiter or parse_regex')
217 if 'delimiter' in engine_settings
and 'parse_regex' in engine_settings:
218 raise ValueError(
'failed to init settings for parsing lines: too many settings')
220 if 'delimiter' in engine_settings:
221 if 'chars' not in engine_settings[
'delimiter']
or 'keys' not in engine_settings[
'delimiter']:
226 """Parses command line output based on configuration"""
231 elements = raw_result.split(delimiter[
'chars'], maxsplit=len(delimiter[
'keys']) - 1)
232 if len(elements) != len(delimiter[
'keys']):
234 for i
in range(len(elements)):
235 result[delimiter[
'keys'][i]] = elements[i]
238 for result_key, regex
in _compiled_parse_regex.items():
239 found = regex.search(raw_result)
242 result[result_key] = raw_result[found.start() : found.end()]
__parse_single_result(raw_result)
__get_results_limits(pageno)
check_parsing_options(engine_settings)
_get_results_from_process(results, cmd, pageno)
_get_command_to_run(query)
__check_query_params(params)