Repository: benbusby/whoogle-search
File: app/routes.py (Python)

Code before:
    ])

def bolden_query_terms_in_response(response,query):
    summary_class='BNeawe s3v9rd AP7Wnd'
    summary_class_beginning=f'<div class="{summary_class}">'
    summary_class_end=r'</div>'
    results=bsoup(response, "html.parser")
    summary = results.find_all("div", {"class": summary_class})
    summary = [str(_).replace(summary_class_beginning,'').replace(summary_class_end,'') for _ in summary if not r'<div><div>' in str(_)]
    quotes=re.findall('"[^"]+"',query)
    not_quotes=re.sub('"[^"]+"','',query)
    not_quotes=list(filter(None,not_quotes.split(' ')))
    search_terms=not_quotes+[_[1:-1] for _ in list(quotes)]
    summary_bolded=[]
    for sentence in summary:
        sentence_temp=sentence
        for term in search_terms:
            sentence_temp=re.sub(fr"\b({term})\b",r"<b>\1</b>",sentence_temp,flags=re.I)
        summary_bolded.append(sentence_temp)

    response_return=response

    for i in range(len(summary)):
        response_return=response_return.replace(summary[i],summary_bolded[i])

    return response_return
@app.route('/search', methods=['GET', 'POST'])
@auth_required
def search():
    # Update user config if specified in search args
    g.user_config = g.user_config.from_params(g.request_params)

    search_util = Search(request, g.user_config, session,
                         cookies_disabled=g.cookies_disabled)
    query = search_util.new_search_query()

    bang = resolve_bang(query=query, bangs_dict=bang_json)
    if bang != '':
        return redirect(bang)

    # Redirect to home if invalid/blank search
    if not query:
        return redirect('/')

    # Generate response and number of external elements from the page
    try:
        response = search_util.generate_response()
    except TorError as e:
        session['error_message'] = e.message + (
            "\\n\\nTor config is now disabled!" if e.disable else "")
        session['config']['tor'] = False if e.disable else session['config'][

Review comment (security):
I started working on condensing this a bit and making it not dependent on classes that Google may change later on. I came up with the following, which still bolds portions of matching words in result URLs, but I don't really see that as a dealbreaker. It's not ideal, but it's probably good enough for now.

```python
def bold_search_terms(response: str, query: str) -> BeautifulSoup:
    """Wraps all search terms in bold tags (<b>). If any terms are wrapped
    in quotes, only that exact phrase will be made bold.

    Args:
        response: The initial response body for the query
        query: The original search query

    Returns:
        BeautifulSoup: modified soup object with bold items
    """
    response = BeautifulSoup(response, 'html.parser')

    def replace_any_case(element: NavigableString, target_word: str) -> None:
        # Replace all instances of the word, but maintaining the same case in
        # the replacement
        element.replace_with(
            element.replace(
                target_word.lower(), f'<b>{target_word.lower()}</b>'
            ).replace(
                target_word.capitalize(), f'<b>{target_word.capitalize()}</b>'
            ).replace(
                target_word.title(), f'<b>{target_word.title()}</b>'
            ).replace(
                target_word.upper(), f'<b>{target_word.upper()}</b>'
            )
        )

    # Split all words out of query, grouping the ones wrapped in quotes
    for word in re.split(r'\s+(?=[^"]*(?:"[^"]*"[^"]*)*$)', query):
        word = re.sub(r'[^A-Za-z0-9 ]+', '', word)
        target = response.find_all(
            text=re.compile(r'' + re.escape(word), re.I))
        for nav_str in target:
            replace_any_case(nav_str, word)

    return response
```

This function should probably also go in `results.py` with the other functions for modifying the search results, rather than in routes. 

Code after:
        g.user_request.autocomplete(q) if not g.user_config.tor else []
    ])


@app.route('/search', methods=['GET', 'POST'])
@auth_required
def search():
    # Update user config if specified in search args
    g.user_config = g.user_config.from_params(g.request_params)

    search_util = Search(request, g.user_config, session,
                         cookies_disabled=g.cookies_disabled)
    query = search_util.new_search_query()

    bang = resolve_bang(query=query, bangs_dict=bang_json)
    if bang != '':
        return redirect(bang)

    # Redirect to home if invalid/blank search
    if not query:
        return redirect('/')

    # Generate response and number of external elements from the page
    try:
        response = search_util.generate_response()
    except TorError as e:
        session['error_message'] = e.message + (
            "\\n\\nTor config is now disabled!" if e.disable else "")
        session['config']['tor'] = False if e.disable else session['config'][
            'tor']
        return redirect(url_for('.index'))

    if search_util.feeling_lucky:
        return redirect(response, code=303)

    # If the user is attempting to translate a string, determine the correct
    # string for formatting the lingva.ml url
    localization_lang = g.user_config.get_localization_lang()
    translation = app.config['TRANSLATIONS'][localization_lang]
    translate_to = localization_lang.replace('lang_', '')

    # Return 503 if temporarily blocked by captcha
    resp_code = 503 if has_captcha(str(response)) else 200
    response = bold_search_terms(response, query)
    # Feature to display IP address
    if search_util.check_kw_ip():
        html_soup = bsoup(response, "html.parser")
        response = add_ip_card(html_soup, request.remote_addr)

    return render_template(
        'display.html',