| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969 | #!/usr/bin/env python'''searx is free software: you can redistribute it and/or modifyit under the terms of the GNU Affero General Public License as published bythe Free Software Foundation, either version 3 of the License, or(at your option) any later version.searx is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See theGNU Affero General Public License for more details.You should have received a copy of the GNU Affero General Public Licensealong with searx. If not, see < http://www.gnu.org/licenses/ >.(C) 2013- by Adam Tauber, <asciimoo@gmail.com>'''if __name__ == '__main__':    from sys import path    from os.path import realpath, dirname    path.append(realpath(dirname(realpath(__file__)) + '/../'))import hashlibimport hmacimport jsonimport osimport sysimport requestsfrom searx import loggerlogger = logger.getChild('webapp')try:    from pygments import highlight    from pygments.lexers import get_lexer_by_name    from pygments.formatters import HtmlFormatterexcept:    logger.critical("cannot import dependency: pygments")    from sys import exit    exit(1)from cgi import escapefrom datetime import datetime, timedeltafrom time import timefrom werkzeug.contrib.fixers import ProxyFixfrom flask import (    Flask, request, render_template, url_for, Response, make_response,    redirect, send_from_directory)from flask_babel import Babel, gettext, format_date, format_decimalfrom flask.json import jsonifyfrom searx import settings, searx_dir, searx_debugfrom searx.exceptions import SearxParameterExceptionfrom searx.engines import (    categories, engines, engine_shortcuts, get_engines_stats, initialize_engines)from searx.utils import (    UnicodeWriter, highlight_content, html_to_text, get_resources_directory,    get_static_files, get_result_templates, get_themes, gen_useragent,    dict_subset, prettify_url, match_language)from searx.version import VERSION_STRINGfrom searx.languages import language_codes as languagesfrom searx.search import SearchWithPlugins, get_search_query_from_webappfrom searx.query import RawTextQueryfrom searx.autocomplete import searx_bang, backends as autocomplete_backendsfrom searx.plugins import pluginsfrom searx.plugins.oa_doi_rewrite import get_doi_resolverfrom searx.preferences import Preferences, ValidationException, LANGUAGE_CODESfrom searx.answerers import answerersfrom searx.url_utils import urlencode, urlparse, urljoinfrom searx.utils import new_hmac# check if the pyopenssl package is installed.# It is needed for SSL connection without trouble, see #298try:    import OpenSSL.SSL  # NOQAexcept ImportError:    logger.critical("The pyopenssl package has to be installed.\n"                    "Some HTTPS connections will fail")try:    from cStringIO import StringIOexcept:    from io import StringIOif sys.version_info[0] == 3:    unicode = str    PY3 = Trueelse:    PY3 = False# serve pages with HTTP/1.1from werkzeug.serving import WSGIRequestHandlerWSGIRequestHandler.protocol_version = "HTTP/{}".format(settings['server'].get('http_protocol_version', '1.0'))# about staticstatic_path = get_resources_directory(searx_dir, 'static', settings['ui']['static_path'])logger.debug('static directory is %s', static_path)static_files = get_static_files(static_path)# about templatesdefault_theme = settings['ui']['default_theme']templates_path = get_resources_directory(searx_dir, 'templates', settings['ui']['templates_path'])logger.debug('templates directory is %s', templates_path)themes = get_themes(templates_path)result_templates = get_result_templates(templates_path)global_favicons = []for indice, theme in enumerate(themes):    global_favicons.append([])    theme_img_path = os.path.join(static_path, 'themes', theme, 'img', 'icons')    for (dirpath, dirnames, filenames) in os.walk(theme_img_path):        global_favicons[indice].extend(filenames)# Flask appapp = Flask(    __name__,    static_folder=static_path,    template_folder=templates_path)app.jinja_env.trim_blocks = Trueapp.jinja_env.lstrip_blocks = Trueapp.secret_key = settings['server']['secret_key']if not searx_debug \   or os.environ.get("WERKZEUG_RUN_MAIN") == "true" \   or os.environ.get('UWSGI_ORIGINAL_PROC_NAME') is not None:    initialize_engines(settings['engines'])babel = Babel(app)rtl_locales = ['ar', 'arc', 'bcc', 'bqi', 'ckb', 'dv', 'fa', 'glk', 'he',               'ku', 'mzn', 'pnb', 'ps', 'sd', 'ug', 'ur', 'yi']# used when translating category names_category_names = (gettext('files'),                   gettext('general'),                   gettext('music'),                   gettext('social media'),                   gettext('images'),                   gettext('videos'),                   gettext('it'),                   gettext('news'),                   gettext('map'),                   gettext('science'))outgoing_proxies = settings['outgoing'].get('proxies') or None@babel.localeselectordef get_locale():    locale = request.accept_languages.best_match(settings['locales'].keys())    if request.preferences.get_value('locale') != '':        locale = request.preferences.get_value('locale')    if 'locale' in request.args\       and request.args['locale'] in settings['locales']:        locale = request.args['locale']    if 'locale' in request.form\       and request.form['locale'] in settings['locales']:        locale = request.form['locale']    return locale# code-highlighter@app.template_filter('code_highlighter')def code_highlighter(codelines, language=None):    if not language:        language = 'text'    try:        # find lexer by programing language        lexer = get_lexer_by_name(language, stripall=True)    except:        # if lexer is not found, using default one        logger.debug('highlighter cannot find lexer for {0}'.format(language))        lexer = get_lexer_by_name('text', stripall=True)    html_code = ''    tmp_code = ''    last_line = None    # parse lines    for line, code in codelines:        if not last_line:            line_code_start = line        # new codeblock is detected        if last_line is not None and\           last_line + 1 != line:            # highlight last codepart            formatter = HtmlFormatter(linenos='inline',                                      linenostart=line_code_start)            html_code = html_code + highlight(tmp_code, lexer, formatter)            # reset conditions for next codepart            tmp_code = ''            line_code_start = line        # add codepart        tmp_code += code + '\n'        # update line        last_line = line    # highlight last codepart    formatter = HtmlFormatter(linenos='inline', linenostart=line_code_start)    html_code = html_code + highlight(tmp_code, lexer, formatter)    return html_code# Extract domain from url@app.template_filter('extract_domain')def extract_domain(url):    return urlparse(url)[1]def get_base_url():    if settings['server']['base_url']:        hostname = settings['server']['base_url']    else:        scheme = 'http'        if request.is_secure:            scheme = 'https'        hostname = url_for('index', _external=True, _scheme=scheme)    return hostnamedef get_current_theme_name(override=None):    """Returns theme name.    Checks in this order:    1. override    2. cookies    3. settings"""    if override and (override in themes or override == '__common__'):        return override    theme_name = request.args.get('theme', request.preferences.get_value('theme'))    if theme_name not in themes:        theme_name = default_theme    return theme_namedef get_result_template(theme, template_name):    themed_path = theme + '/result_templates/' + template_name    if themed_path in result_templates:        return themed_path    return 'result_templates/' + template_namedef url_for_theme(endpoint, override_theme=None, **values):    if endpoint == 'static' and values.get('filename'):        theme_name = get_current_theme_name(override=override_theme)        filename_with_theme = "themes/{}/{}".format(theme_name, values['filename'])        if filename_with_theme in static_files:            values['filename'] = filename_with_theme    return url_for(endpoint, **values)def proxify(url):    if url.startswith('//'):        url = 'https:' + url    if not settings.get('result_proxy'):        return url    url_params = dict(mortyurl=url.encode('utf-8'))    if settings['result_proxy'].get('key'):        url_params['mortyhash'] = hmac.new(settings['result_proxy']['key'],                                           url.encode('utf-8'),                                           hashlib.sha256).hexdigest()    return '{0}?{1}'.format(settings['result_proxy']['url'],                            urlencode(url_params))def image_proxify(url):    if url.startswith('//'):        url = 'https:' + url    if not request.preferences.get_value('image_proxy'):        return url    if url.startswith('data:image/jpeg;base64,'):        return url    if settings.get('result_proxy'):        return proxify(url)    h = new_hmac(settings['server']['secret_key'], url.encode('utf-8'))    return '{0}?{1}'.format(url_for('image_proxy'),                            urlencode(dict(url=url.encode('utf-8'), h=h)))def render(template_name, override_theme=None, **kwargs):    disabled_engines = request.preferences.engines.get_disabled()    enabled_categories = set(category for engine_name in engines                             for category in engines[engine_name].categories                             if (engine_name, category) not in disabled_engines)    if 'categories' not in kwargs:        kwargs['categories'] = ['general']        kwargs['categories'].extend(x for x in                                    sorted(categories.keys())                                    if x != 'general'                                    and x in enabled_categories)    if 'all_categories' not in kwargs:        kwargs['all_categories'] = ['general']        kwargs['all_categories'].extend(x for x in                                        sorted(categories.keys())                                        if x != 'general')    if 'selected_categories' not in kwargs:        kwargs['selected_categories'] = []        for arg in request.args:            if arg.startswith('category_'):                c = arg.split('_', 1)[1]                if c in categories:                    kwargs['selected_categories'].append(c)    if not kwargs['selected_categories']:        cookie_categories = request.preferences.get_value('categories')        for ccateg in cookie_categories:            kwargs['selected_categories'].append(ccateg)    if not kwargs['selected_categories']:        kwargs['selected_categories'] = ['general']    if 'autocomplete' not in kwargs:        kwargs['autocomplete'] = request.preferences.get_value('autocomplete')    if get_locale() in rtl_locales and 'rtl' not in kwargs:        kwargs['rtl'] = True    kwargs['searx_version'] = VERSION_STRING    kwargs['method'] = request.preferences.get_value('method')    kwargs['safesearch'] = str(request.preferences.get_value('safesearch'))    kwargs['language_codes'] = languages    if 'current_language' not in kwargs:        kwargs['current_language'] = match_language(request.preferences.get_value('language'),                                                    LANGUAGE_CODES,                                                    fallback=settings['search']['language'])    # override url_for function in templates    kwargs['url_for'] = url_for_theme    kwargs['image_proxify'] = image_proxify    kwargs['proxify'] = proxify if settings.get('result_proxy', {}).get('url') else None    kwargs['get_result_template'] = get_result_template    kwargs['theme'] = get_current_theme_name(override=override_theme)    kwargs['template_name'] = template_name    kwargs['cookies'] = request.cookies    kwargs['errors'] = request.errors    kwargs['instance_name'] = settings['general']['instance_name']    kwargs['results_on_new_tab'] = request.preferences.get_value('results_on_new_tab')    kwargs['unicode'] = unicode    kwargs['preferences'] = request.preferences    kwargs['scripts'] = set()    for plugin in request.user_plugins:        for script in plugin.js_dependencies:            kwargs['scripts'].add(script)    kwargs['styles'] = set()    for plugin in request.user_plugins:        for css in plugin.css_dependencies:            kwargs['styles'].add(css)    return render_template(        '{}/{}'.format(kwargs['theme'], template_name), **kwargs)@app.before_requestdef pre_request():    request.start_time = time()    request.timings = []    request.errors = []    preferences = Preferences(themes, list(categories.keys()), engines, plugins)    request.preferences = preferences    try:        preferences.parse_dict(request.cookies)    except:        request.errors.append(gettext('Invalid settings, please edit your preferences'))    # merge GET, POST vars    # request.form    request.form = dict(request.form.items())    for k, v in request.args.items():        if k not in request.form:            request.form[k] = v    if request.form.get('preferences'):        preferences.parse_encoded_data(request.form['preferences'])    else:        try:            preferences.parse_dict(request.form)        except Exception as e:            logger.exception('invalid settings')            request.errors.append(gettext('Invalid settings'))    # request.user_plugins    request.user_plugins = []    allowed_plugins = preferences.plugins.get_enabled()    disabled_plugins = preferences.plugins.get_disabled()    for plugin in plugins:        if ((plugin.default_on and plugin.id not in disabled_plugins)                or plugin.id in allowed_plugins):            request.user_plugins.append(plugin)@app.after_requestdef post_request(response):    total_time = time() - request.start_time    timings_all = ['total;dur=' + str(round(total_time * 1000, 3))]    if len(request.timings) > 0:        timings = sorted(request.timings, key=lambda v: v['total'])        timings_total = ['total_' + str(i) + '_' + v['engine'] +                         ';dur=' + str(round(v['total'] * 1000, 3)) for i, v in enumerate(timings)]        timings_load = ['load_' + str(i) + '_' + v['engine'] +                        ';dur=' + str(round(v['load'] * 1000, 3)) for i, v in enumerate(timings)]        timings_all = timings_all + timings_total + timings_load    response.headers.add('Server-Timing', ', '.join(timings_all))    return responsedef index_error(output_format, error_message):    if output_format == 'json':        return Response(json.dumps({'error': error_message}),                        mimetype='application/json')    elif output_format == 'csv':        response = Response('', mimetype='application/csv')        cont_disp = 'attachment;Filename=searx.csv'        response.headers.add('Content-Disposition', cont_disp)        return response    elif output_format == 'rss':        response_rss = render(            'opensearch_response_rss.xml',            results=[],            q=request.form['q'] if 'q' in request.form else '',            number_of_results=0,            base_url=get_base_url(),            error_message=error_message,            override_theme='__common__',        )        return Response(response_rss, mimetype='text/xml')    else:        # html        request.errors.append(gettext('search error'))        return render(            'index.html',        )@app.route('/search', methods=['GET', 'POST'])@app.route('/', methods=['GET', 'POST'])def index():    """Render index page.    Supported outputs: html, json, csv, rss.    """    # output_format    output_format = request.form.get('format', 'html')    if output_format not in ['html', 'csv', 'json', 'rss']:        output_format = 'html'    # check if there is query    if request.form.get('q') is None:        if output_format == 'html':            return render(                'index.html',            )        else:            return index_error(output_format, 'No query'), 400    # search    search_query = None    raw_text_query = None    result_container = None    try:        search_query, raw_text_query = get_search_query_from_webapp(request.preferences, request.form)        # search = Search(search_query) #  without plugins        search = SearchWithPlugins(search_query, request.user_plugins, request)        result_container = search.search()    except Exception as e:        # log exception        logger.exception('search error')        # is it an invalid input parameter or something else ?        if (issubclass(e.__class__, SearxParameterException)):            return index_error(output_format, e.message), 400        else:            return index_error(output_format, gettext('search error')), 500    # results    results = result_container.get_ordered_results()    number_of_results = result_container.results_number()    if number_of_results < result_container.results_length():        number_of_results = 0    # UI    advanced_search = request.form.get('advanced_search', None)    # Server-Timing header    request.timings = result_container.get_timings()    # output    for result in results:        if output_format == 'html':            if 'content' in result and result['content']:                result['content'] = highlight_content(escape(result['content'][:1024]), search_query.query)            result['title'] = highlight_content(escape(result['title'] or u''), search_query.query)        else:            if result.get('content'):                result['content'] = html_to_text(result['content']).strip()            # removing html content and whitespace duplications            result['title'] = ' '.join(html_to_text(result['title']).strip().split())        result['pretty_url'] = prettify_url(result['url'])        # TODO, check if timezone is calculated right        if 'publishedDate' in result:            try:  # test if publishedDate >= 1900 (datetime module bug)                result['pubdate'] = result['publishedDate'].strftime('%Y-%m-%d %H:%M:%S%z')            except ValueError:                result['publishedDate'] = None            else:                if result['publishedDate'].replace(tzinfo=None) >= datetime.now() - timedelta(days=1):                    timedifference = datetime.now() - result['publishedDate'].replace(tzinfo=None)                    minutes = int((timedifference.seconds / 60) % 60)                    hours = int(timedifference.seconds / 60 / 60)                    if hours == 0:                        result['publishedDate'] = gettext(u'{minutes} minute(s) ago').format(minutes=minutes)                    else:                        result['publishedDate'] = gettext(u'{hours} hour(s), {minutes} minute(s) ago').format(hours=hours, minutes=minutes)  # noqa                else:                    result['publishedDate'] = format_date(result['publishedDate'])    if output_format == 'json':        return Response(json.dumps({'query': search_query.query.decode('utf-8'),                                    'number_of_results': number_of_results,                                    'results': results,                                    'answers': list(result_container.answers),                                    'corrections': list(result_container.corrections),                                    'infoboxes': result_container.infoboxes,                                    'suggestions': list(result_container.suggestions),                                    'unresponsive_engines': list(result_container.unresponsive_engines)},                                   default=lambda item: list(item) if isinstance(item, set) else item),                        mimetype='application/json')    elif output_format == 'csv':        csv = UnicodeWriter(StringIO())        keys = ('title', 'url', 'content', 'host', 'engine', 'score')        csv.writerow(keys)        for row in results:            row['host'] = row['parsed_url'].netloc            csv.writerow([row.get(key, '') for key in keys])        csv.stream.seek(0)        response = Response(csv.stream.read(), mimetype='application/csv')        cont_disp = 'attachment;Filename=searx_-_{0}.csv'.format(search_query.query)        response.headers.add('Content-Disposition', cont_disp)        return response    elif output_format == 'rss':        response_rss = render(            'opensearch_response_rss.xml',            results=results,            q=request.form['q'],            number_of_results=number_of_results,            base_url=get_base_url(),            override_theme='__common__',        )        return Response(response_rss, mimetype='text/xml')    # HTML output format    # suggestions: use RawTextQuery to get the suggestion URLs with the same bang    suggestion_urls = map(lambda suggestion: {                          'url': raw_text_query.changeSearchQuery(suggestion).getFullQuery(),                          'title': suggestion                          },                          result_container.suggestions)    #    return render(        'results.html',        results=results,        q=request.form['q'],        selected_categories=search_query.categories,        pageno=search_query.pageno,        time_range=search_query.time_range,        number_of_results=format_decimal(number_of_results),        advanced_search=advanced_search,        suggestions=suggestion_urls,        answers=result_container.answers,        corrections=result_container.corrections,        infoboxes=result_container.infoboxes,        paging=result_container.paging,        unresponsive_engines=result_container.unresponsive_engines,        current_language=match_language(search_query.lang,                                        LANGUAGE_CODES,                                        fallback=settings['search']['language']),        base_url=get_base_url(),        theme=get_current_theme_name(),        favicons=global_favicons[themes.index(get_current_theme_name())],        timeout_limit=request.form.get('timeout_limit', None)    )@app.route('/about', methods=['GET'])def about():    """Render about page"""    return render(        'about.html',    )@app.route('/autocompleter', methods=['GET', 'POST'])def autocompleter():    """Return autocompleter results"""    # set blocked engines    disabled_engines = request.preferences.engines.get_disabled()    # parse query    if PY3:        raw_text_query = RawTextQuery(request.form.get('q', b''), disabled_engines)    else:        raw_text_query = RawTextQuery(request.form.get('q', u'').encode('utf-8'), disabled_engines)    raw_text_query.parse_query()    # check if search query is set    if not raw_text_query.getSearchQuery():        return '', 400    # run autocompleter    completer = autocomplete_backends.get(request.preferences.get_value('autocomplete'))    # parse searx specific autocompleter results like !bang    raw_results = searx_bang(raw_text_query)    # normal autocompletion results only appear if no inner results returned    # and there is a query part besides the engine and language bangs    if len(raw_results) == 0 and completer and (len(raw_text_query.query_parts) > 1 or                                                (len(raw_text_query.languages) == 0 and                                                 not raw_text_query.specific)):        # get language from cookie        language = request.preferences.get_value('language')        if not language or language == 'all':            language = 'en'        else:            language = language.split('-')[0]        # run autocompletion        raw_results.extend(completer(raw_text_query.getSearchQuery(), language))    # parse results (write :language and !engine back to result string)    results = []    for result in raw_results:        raw_text_query.changeSearchQuery(result)        # add parsed result        results.append(raw_text_query.getFullQuery())    # return autocompleter results    if request.form.get('format') == 'x-suggestions':        return Response(json.dumps([raw_text_query.query, results]),                        mimetype='application/json')    return Response(json.dumps(results),                    mimetype='application/json')@app.route('/preferences', methods=['GET', 'POST'])def preferences():    """Render preferences page && save user preferences"""    # save preferences    if request.method == 'POST':        resp = make_response(redirect(urljoin(settings['server']['base_url'], url_for('index'))))        try:            request.preferences.parse_form(request.form)        except ValidationException:            request.errors.append(gettext('Invalid settings, please edit your preferences'))            return resp        return request.preferences.save(resp)    # render preferences    image_proxy = request.preferences.get_value('image_proxy')    lang = request.preferences.get_value('language')    disabled_engines = request.preferences.engines.get_disabled()    allowed_plugins = request.preferences.plugins.get_enabled()    # stats for preferences page    stats = {}    for c in categories:        for e in categories[c]:            stats[e.name] = {'time': None,                             'warn_timeout': False,                             'warn_time': False}            if e.timeout > settings['outgoing']['request_timeout']:                stats[e.name]['warn_timeout'] = True            stats[e.name]['supports_selected_language'] = _is_selected_language_supported(e, request.preferences)    # get first element [0], the engine time,    # and then the second element [1] : the time (the first one is the label)    for engine_stat in get_engines_stats()[0][1]:        stats[engine_stat.get('name')]['time'] = round(engine_stat.get('avg'), 3)        if engine_stat.get('avg') > settings['outgoing']['request_timeout']:            stats[engine_stat.get('name')]['warn_time'] = True    # end of stats    return render('preferences.html',                  locales=settings['locales'],                  current_locale=get_locale(),                  image_proxy=image_proxy,                  engines_by_category=categories,                  stats=stats,                  answerers=[{'info': a.self_info(), 'keywords': a.keywords} for a in answerers],                  disabled_engines=disabled_engines,                  autocomplete_backends=autocomplete_backends,                  shortcuts={y: x for x, y in engine_shortcuts.items()},                  themes=themes,                  plugins=plugins,                  doi_resolvers=settings['doi_resolvers'],                  current_doi_resolver=get_doi_resolver(request.args, request.preferences.get_value('doi_resolver')),                  allowed_plugins=allowed_plugins,                  theme=get_current_theme_name(),                  preferences_url_params=request.preferences.get_as_url_params(),                  base_url=get_base_url(),                  preferences=True)def _is_selected_language_supported(engine, preferences):    language = preferences.get_value('language')    return (language == 'all'            or match_language(language,                              getattr(engine, 'supported_languages', []),                              getattr(engine, 'language_aliases', {}), None))@app.route('/image_proxy', methods=['GET'])def image_proxy():    url = request.args.get('url').encode('utf-8')    if not url:        return '', 400    h = new_hmac(settings['server']['secret_key'], url)    if h != request.args.get('h'):        return '', 400    headers = dict_subset(request.headers, {'If-Modified-Since', 'If-None-Match'})    headers['User-Agent'] = gen_useragent()    resp = requests.get(url,                        stream=True,                        timeout=settings['outgoing']['request_timeout'],                        headers=headers,                        proxies=outgoing_proxies)    if resp.status_code == 304:        return '', resp.status_code    if resp.status_code != 200:        logger.debug('image-proxy: wrong response code: {0}'.format(resp.status_code))        if resp.status_code >= 400:            return '', resp.status_code        return '', 400    if not resp.headers.get('content-type', '').startswith('image/'):        logger.debug('image-proxy: wrong content-type: {0}'.format(resp.headers.get('content-type')))        return '', 400    img = b''    chunk_counter = 0    for chunk in resp.iter_content(1024 * 1024):        chunk_counter += 1        if chunk_counter > 5:            return '', 502  # Bad gateway - file is too big (>5M)        img += chunk    headers = dict_subset(resp.headers, {'Content-Length', 'Length', 'Date', 'Last-Modified', 'Expires', 'Etag'})    return Response(img, mimetype=resp.headers['content-type'], headers=headers)@app.route('/stats', methods=['GET'])def stats():    """Render engine statistics page."""    stats = get_engines_stats()    return render(        'stats.html',        stats=stats,    )@app.route('/robots.txt', methods=['GET'])def robots():    return Response("""User-agent: *Allow: /Allow: /aboutDisallow: /statsDisallow: /preferencesDisallow: /*?*q=*""", mimetype='text/plain')@app.route('/opensearch.xml', methods=['GET'])def opensearch():    method = 'post'    if request.preferences.get_value('method') == 'GET':        method = 'get'    # chrome/chromium only supports HTTP GET....    if request.headers.get('User-Agent', '').lower().find('webkit') >= 0:        method = 'get'    ret = render('opensearch.xml',                 opensearch_method=method,                 host=get_base_url(),                 urljoin=urljoin,                 override_theme='__common__')    resp = Response(response=ret,                    status=200,                    mimetype="text/xml")    return resp@app.route('/favicon.ico')def favicon():    return send_from_directory(os.path.join(app.root_path,                                            static_path,                                            'themes',                                            get_current_theme_name(),                                            'img'),                               'favicon.png',                               mimetype='image/vnd.microsoft.icon')@app.route('/clear_cookies')def clear_cookies():    resp = make_response(redirect(urljoin(settings['server']['base_url'], url_for('index'))))    for cookie_name in request.cookies:        resp.delete_cookie(cookie_name)    return resp@app.route('/config')def config():    return jsonify({'categories': list(categories.keys()),                    'engines': [{'name': engine_name,                                 'categories': engine.categories,                                 'shortcut': engine.shortcut,                                 'enabled': not engine.disabled,                                 'paging': engine.paging,                                 'language_support': engine.language_support,                                 'supported_languages':                                 list(engine.supported_languages.keys())                                 if isinstance(engine.supported_languages, dict)                                 else engine.supported_languages,                                 'safesearch': engine.safesearch,                                 'time_range_support': engine.time_range_support,                                 'timeout': engine.timeout}                                for engine_name, engine in engines.items()],                    'plugins': [{'name': plugin.name,                                 'enabled': plugin.default_on}                                for plugin in plugins],                    'instance_name': settings['general']['instance_name'],                    'locales': settings['locales'],                    'default_locale': settings['ui']['default_locale'],                    'autocomplete': settings['search']['autocomplete'],                    'safe_search': settings['search']['safe_search'],                    'default_theme': settings['ui']['default_theme'],                    'version': VERSION_STRING,                    'doi_resolvers': [r for r in settings['doi_resolvers']],                    'default_doi_resolver': settings['default_doi_resolver'],                    })@app.errorhandler(404)def page_not_found(e):    return render('404.html'), 404def run():    logger.debug('starting webserver on %s:%s', settings['server']['bind_address'], settings['server']['port'])    app.run(        debug=searx_debug,        use_debugger=searx_debug,        port=settings['server']['port'],        host=settings['server']['bind_address'],        threaded=True    )class ReverseProxyPathFix(object):    '''Wrap the application in this middleware and configure the    front-end server to add these headers, to let you quietly bind    this to a URL other than / and to an HTTP scheme that is    different than what is used locally.    http://flask.pocoo.org/snippets/35/    In nginx:    location /myprefix {        proxy_pass http://127.0.0.1:8000;        proxy_set_header Host $host;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_set_header X-Scheme $scheme;        proxy_set_header X-Script-Name /myprefix;        }    :param app: the WSGI application    '''    def __init__(self, app):        self.app = app    def __call__(self, environ, start_response):        script_name = environ.get('HTTP_X_SCRIPT_NAME', '')        if script_name:            environ['SCRIPT_NAME'] = script_name            path_info = environ['PATH_INFO']            if path_info.startswith(script_name):                environ['PATH_INFO'] = path_info[len(script_name):]        scheme = environ.get('HTTP_X_SCHEME', '')        if scheme:            environ['wsgi.url_scheme'] = scheme        return self.app(environ, start_response)application = app# patch app to handle non root url-s behind proxy & wsgiapp.wsgi_app = ReverseProxyPathFix(ProxyFix(application.wsgi_app))if __name__ == "__main__":    run()
 |